mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix: surface hidden-pane steer failures and demote per-turn gateway log noise (#124560)
* fix(ui): surface hidden-pane steer terminal failures globally Three terminal branches in steer-lifecycle.ts (transport null result, failed queue-row restore, failed queue-row removal) still gated their error on itemStillVisible, so a steer that failed after the operator navigated away parked the error on the queue row with no visible outcome — the exact invariant #124473 introduced surfaceChatDeliveryFailure() to protect. Route all three through the canonical helper and delete the divergent visibility-only branches. Regression test fails pre-fix (stash-verified): steer transport failure with the pane hidden now surfaces the session-named global toast. * fix(logging): demote per-turn gateway log noise to debug Live campaign evidence showed three lines dominating operator logs at info level with no per-turn diagnostic value: - 'tool policy removed N tool(s)': the policy pipeline runs on every turn, so this repeated 42x in one session. Demote to debug and delete the now-dead toolPolicyAuditLogLevel/auditLogLevel plumbing that only existed to lower diagnostic probes to the level that is now the default (net -13 production LOC). - 'codex app-server one-shot cleanup checked shared client retirement': routine per-attempt teardown detail; demote to debug. - 'codex trajectory capture requires the SQLite host recorder': static config condition warned per attempt; warn once per process. Skipped: the [model-fetch] info carve-out in model-transport-debug.ts is a named contract (docs/logging.md, #89648) — always-info by design. * fix(codex): drop test-only trajectory warn-once reset export Knip's production unused-export gate rejects resetCodexTrajectoryRecorderWarningForTest — it was a test-only seam in production code. Reset the process-wide warn-once flag via vi.resetModules() + fresh dynamic import in the test instead. * test(cron): wait for backoff re-arm instead of fixed sleep The 0ms retry timer arms only after async watcher-state persistence, so 'await delay(5)' races it on loaded CI workers (flaked on checks-node-compact-large-2: spawn called 1 time, expected 2). Replace both fixed-sleep re-arm waits with vi.waitFor on the spawn count. The remaining delay(5) guards a negative no-further-spawn assertion after cancel, where a bounded sleep is the correct shape. * fix(codex): scope trajectory recorder warn dedupe to session ClawSweeper P2: the host recorder factory returns null for per-session target-mapping conflicts, not only static config, so a process-wide warn-once flag silenced a later distinct session's recorder loss. Warn once per session (bounded set, cleared past 64 entries) so retries stay quiet but each newly affected session records its loss. Regression covers a later distinct session still warning.
This commit is contained in:
committed by
GitHub
parent
243f51d314
commit
7173aeb663
@@ -137,7 +137,9 @@ export function prepareCodexAttemptResources(prompt: CodexAttemptPrompt) {
|
||||
}
|
||||
state.sharedCodexClientRetiredForOneShotCleanup = true;
|
||||
const retired = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(state.client);
|
||||
embeddedAgentLog.info("codex app-server one-shot cleanup checked shared client retirement", {
|
||||
// Runs on every one-shot attempt teardown; routine retirement checks are
|
||||
// diagnostic detail, not operator-facing info.
|
||||
embeddedAgentLog.debug("codex app-server one-shot cleanup checked shared client retirement", {
|
||||
runId: params.runId,
|
||||
sessionId: params.sessionId,
|
||||
sessionKey: params.sessionKey,
|
||||
|
||||
@@ -117,24 +117,41 @@ function createSqliteHostTrajectoryRecorder(params: {
|
||||
}
|
||||
|
||||
describe("Codex trajectory recorder", () => {
|
||||
it("warns when the SQLite host recorder is unavailable", () => {
|
||||
it("warns once per session when the SQLite host recorder is unavailable", async () => {
|
||||
// Import a fresh module instance so the process-wide dedupe set starts
|
||||
// clean without a test-only reset export in production code.
|
||||
vi.resetModules();
|
||||
const { createCodexTrajectoryRecorder: createFreshRecorder } = await import("./trajectory.js");
|
||||
const warn = vi.fn();
|
||||
const recorder = createCodexTrajectoryRecorder({
|
||||
cwd: testWorkspace.dir,
|
||||
attempt: {
|
||||
sessionFile: "agent:main:session-1",
|
||||
sessionId: "session-1",
|
||||
model: { api: "responses" },
|
||||
} as never,
|
||||
env: {},
|
||||
warn,
|
||||
});
|
||||
const makeRecorder = (sessionId: string) =>
|
||||
createFreshRecorder({
|
||||
cwd: testWorkspace.dir,
|
||||
attempt: {
|
||||
sessionFile: `agent:main:${sessionId}`,
|
||||
sessionId,
|
||||
model: { api: "responses" },
|
||||
} as never,
|
||||
env: {},
|
||||
warn,
|
||||
});
|
||||
|
||||
expect(recorder).toBeNull();
|
||||
expect(makeRecorder("session-1")).toBeNull();
|
||||
// Retried attempts for the same session must not repeat the warn.
|
||||
expect(makeRecorder("session-1")).toBeNull();
|
||||
expect(warn).toHaveBeenCalledTimes(1);
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
"codex trajectory capture requires the SQLite host recorder",
|
||||
{ sessionId: "session-1", reason: "sqlite-recorder-unavailable" },
|
||||
);
|
||||
|
||||
// A later distinct session's recorder loss stays visible: the host can
|
||||
// reject per-session (target-mapping conflicts), not only per-process.
|
||||
expect(makeRecorder("session-2")).toBeNull();
|
||||
expect(warn).toHaveBeenCalledTimes(2);
|
||||
expect(warn).toHaveBeenLastCalledWith(
|
||||
"codex trajectory capture requires the SQLite host recorder",
|
||||
{ sessionId: "session-2", reason: "sqlite-recorder-unavailable" },
|
||||
);
|
||||
});
|
||||
|
||||
it("stores SQLite-backed captures for the canonical session-key target", async () => {
|
||||
|
||||
@@ -121,6 +121,13 @@ function createCodexHostTrajectorySink(params: {
|
||||
};
|
||||
}
|
||||
|
||||
// The host recorder can be absent per session (target-mapping conflicts), not
|
||||
// only per process, so dedupe the warn by session: repeated attempts for one
|
||||
// session stay quiet while a later distinct session still records its loss.
|
||||
// Bounded: cleared past the cap so a pathological session churn cannot grow it.
|
||||
const warnedRecorderUnavailableSessions = new Set<string>();
|
||||
const WARNED_RECORDER_SESSIONS_CAP = 64;
|
||||
|
||||
/** Creates a trajectory recorder when trajectory capture is enabled for the environment. */
|
||||
export function createCodexTrajectoryRecorder(
|
||||
params: CodexTrajectoryInit,
|
||||
@@ -136,10 +143,18 @@ export function createCodexTrajectoryRecorder(
|
||||
// from a session-file string silently drops every capture once the host
|
||||
// stops emitting the legacy `sqlite:` marker.
|
||||
if (!params.trajectoryRecorder) {
|
||||
params.warn?.("codex trajectory capture requires the SQLite host recorder", {
|
||||
sessionId: params.attempt.sessionId,
|
||||
reason: "sqlite-recorder-unavailable",
|
||||
});
|
||||
// Per-attempt repeats for one session bury real diagnostics; warn once per
|
||||
// session so retries stay quiet but each newly affected session is visible.
|
||||
if (!warnedRecorderUnavailableSessions.has(params.attempt.sessionId)) {
|
||||
if (warnedRecorderUnavailableSessions.size >= WARNED_RECORDER_SESSIONS_CAP) {
|
||||
warnedRecorderUnavailableSessions.clear();
|
||||
}
|
||||
warnedRecorderUnavailableSessions.add(params.attempt.sessionId);
|
||||
params.warn?.("codex trajectory capture requires the SQLite host recorder", {
|
||||
sessionId: params.attempt.sessionId,
|
||||
reason: "sqlite-recorder-unavailable",
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const sink = createCodexHostTrajectorySink({ recorder: params.trajectoryRecorder });
|
||||
|
||||
Reference in New Issue
Block a user