Skip to content

Commit eee06d1

Browse files
committed
fix(logging): restore terminal follow-up failure log
A final steer whose promise rejects was silent after settlement. Log that failure again with the same id and error shape as the handoff path, and drop the narration comments added with the collapse.
1 parent e297086 commit eee06d1

7 files changed

Lines changed: 42 additions & 40 deletions

File tree

‎src/logging/caught.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// Shared hunter for the "best-effort step failed" debug catch: callers keep
2-
// their message; the helper owns the caught-value normalization-and-append
3-
// shape so that boilerplate exists once, not per site.
41
interface DebugSink {
52
debug: (message: string, properties?: Record<string, unknown>) => void;
63
}

‎src/permission/approval-store-migration.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ export async function migratePersistedApprovalStores(
192192
});
193193
}
194194
}
195-
// One summary warn per migration, not one per store file.
196195
const failed = files.filter((file) => file.error !== undefined);
197196
if (failed.length > 0) {
198197
log.warn("Skipped approval-store migration: {details}", {

‎src/session/runtime-assembly.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,7 @@ export async function loadSeededApprovals(
149149
// residual throw from breaking session start.
150150
try {
151151
await migratePersistedApprovalStores(cwd, sessionId, home);
152-
} catch {
153-
// Swallowed on purpose: the migration already surfaced the cause.
154-
}
152+
} catch {}
155153
const sessionApprovals = await loadApprovals(cwd, sessionId, home);
156154
const [
157155
projectApprovals,

‎src/subagent/session-store.test.ts‎

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { describe, expect, test } from "bun:test";
1+
import { describe, expect, spyOn, test } from "bun:test";
2+
import { getLogger } from "@intx/log";
3+
import { LOG_NAMESPACE_ROOT } from "../branding.js";
24

35
import {
46
createSubAgentSessionStore,
@@ -656,24 +658,36 @@ describe("CL-6943 reusable worker sessions", () => {
656658
});
657659

658660
test("rejected followup restores strip status so interrupt_agent fails closed", async () => {
659-
const store = createSubAgentSessionStore();
660-
const session = retainedSession(store, {
661-
interrupt: true,
662-
followup: async () => {
663-
throw new Error("send failed");
664-
},
665-
complete: "## Summary\nDone.",
666-
});
667-
resumeCompleted(store, session.id);
668-
await new Promise((resolve) => setTimeout(resolve, 0));
669-
670-
const after = store.get(session.id);
671-
expect(after?.status).toBe("done");
672-
expect(after?.lifecycleStatus).toBe("completed");
673-
expect(store.interruptOne(session.id)).toEqual({
674-
ok: false,
675-
status: "completed",
676-
});
661+
const errorLog = spyOn(
662+
getLogger([LOG_NAMESPACE_ROOT, "subagent", "session-store"]),
663+
"error",
664+
);
665+
try {
666+
const store = createSubAgentSessionStore();
667+
const session = retainedSession(store, {
668+
interrupt: true,
669+
followup: async () => {
670+
throw new Error("send failed");
671+
},
672+
complete: "## Summary\nDone.",
673+
});
674+
resumeCompleted(store, session.id);
675+
await new Promise((resolve) => setTimeout(resolve, 0));
676+
677+
const after = store.get(session.id);
678+
expect(after?.status).toBe("done");
679+
expect(after?.lifecycleStatus).toBe("completed");
680+
expect(store.interruptOne(session.id)).toEqual({
681+
ok: false,
682+
status: "completed",
683+
});
684+
expect(errorLog).toHaveBeenCalledWith(
685+
"followup turn failed for {id}: {error}",
686+
{ id: session.id, error: "send failed" },
687+
);
688+
} finally {
689+
errorLog.mockRestore();
690+
}
677691
});
678692

679693
test("rejected followup after interrupt restamps stopReason interrupted", async () => {

‎src/subagent/session-store.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,10 @@ export function createSubAgentSessionStore(
12891289
}
12901290
endFollowupTurn(id, failLifecycle);
12911291
pendingFailures.delete(id);
1292+
log.error("followup turn failed for {id}: {error}", {
1293+
id,
1294+
error: errorMessage(err),
1295+
});
12921296
};
12931297
const queueFollowupTurn = (
12941298
id: string,

‎src/trust/project-trust.ts‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ const emptyStore = (): ProjectTrustStore => ({
5252

5353
/**
5454
* Extract a schema-confirmed array field: missing → [], non-strings dropped.
55-
* Hand-edited partial files must not wipe consent. Issues accumulate on
56-
* `issues` for the caller's one summary warn per store.
55+
* Hand-edited partial files must not wipe consent.
5756
*/
5857
function extractStringArrayField(
5958
value: unknown[] | undefined,
@@ -147,8 +146,6 @@ export async function readProjectTrustStore(
147146
logger.warn`project trust store has an invalid shape at ${path}: ${validated.summary}`;
148147
return { state: "invalid", store: emptyStore() };
149148
}
150-
// Field/entry issues collect; the store gets one summary warn, not one
151-
// per field or entry.
152149
const issues: string[] = [];
153150
const trustedPluginPaths = extractStringArrayField(
154151
validated.trustedPluginPaths,
@@ -193,7 +190,6 @@ export async function readProjectTrustStore(
193190
`trustedPluginPaths: dropped ${nonAbsolute} non-absolute entries`,
194191
);
195192
}
196-
// One summary warn per store, not one warn per field or per entry.
197193
if (issues.length > 0) {
198194
logger.warn`project trust store issues at ${path} (${issues.length}): ${issues.join("; ")}`;
199195
}

‎src/tui/runner/submit.ts‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,14 @@ import {
5555
type RunnerState,
5656
} from "./state.js";
5757
import { LOG_NAMESPACE_ROOT } from "../../branding.js";
58+
import { caughtDebug } from "../../logging/caught.js";
5859
import { buildCredentialRecoveryAlternatives } from "./credential-recovery.js";
5960
import type { PendingCredentialRecovery } from "./credential-recovery.js";
6061
import type { PendingReconnectRecovery } from "./reconnect-recovery.js";
6162
import { listCommands } from "../commands/registry.js";
6263

6364
const tuiLogger = getLogger([LOG_NAMESPACE_ROOT, "tui"]);
6465

65-
// Shared tail for both recordSent paths; append is best-effort recall.
66-
const logSentAppendFailure = (err: unknown): void => {
67-
tuiLogger.debug("sent-message append failed: {error}", {
68-
error: err instanceof Error ? err.message : String(err),
69-
});
70-
};
71-
7266
export type SubmissionRoute =
7367
| { kind: "empty" }
7468
| { kind: "command"; name: string; args: string }
@@ -454,7 +448,7 @@ export function createSubmitPath(
454448
state.sendAborted = false;
455449
if (text.trim().length > 0) {
456450
void appendSentMessage(state.config.cwd, state.sessionId, text).catch(
457-
logSentAppendFailure,
451+
caughtDebug(tuiLogger, "sent-message append failed"),
458452
);
459453
}
460454
const ingested = await ingestOperatorPrompt(
@@ -550,7 +544,7 @@ export function createDeliverRouting(
550544
if (text.startsWith(ASK_DIRECTOR_WAKE_PREFIX)) return;
551545
if (text.startsWith(MAILBOX_MAIL_WAKE_PREFIX)) return;
552546
void appendSentMessage(state.config.cwd, state.sessionId, text).catch(
553-
logSentAppendFailure,
547+
caughtDebug(tuiLogger, "sent-message append failed"),
554548
);
555549
},
556550
captureGeneration: services.deliveryGeneration.capture,

0 commit comments

Comments
 (0)