From 9810a69c446b78dbe65390260b82532d38ebafff Mon Sep 17 00:00:00 2001 From: omar7550 <76748259+omar7550@users.noreply.github.com> Date: Wed, 14 Oct 2026 14:40:21 -0400 Subject: [PATCH] fix(server): preserve buffered Claude usage-limit failures --- .../Adapters/ClaudeAdapterV2.test.ts | 17 ++++++++++++++++- .../Adapters/ClaudeAdapterV2.ts | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index edaafff081e2e..ef3d4469122ab 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -6468,6 +6468,21 @@ describe("ClaudeAdapterV2 background wake turns", () => { ); assert.lengthOf(harness.continuationRequests, 1); + // Session-task lifecycle results arrive before T3 opens the wake run. + // They must not replace the root's terminal usage-limit result. + for (const index of [1, 2, 3]) { + yield* harness.offerAndWait(wakeTurnInit); + yield* harness.offerAndWait( + makeResultFrame({ + uuid: `00000000-0000-4000-8000-00000000064${index}`, + result: "", + numTurns: 0, + origin: { kind: "task-notification" }, + terminalReason: null, + }), + ); + } + yield* harness.runtime.startTurn( makeClaudeTestTurnInput({ threadId: harness.threadId, @@ -6813,7 +6828,7 @@ describe("ClaudeAdapterV2 background wake turns", () => { harness.sdkMessages, makeResultFrame({ uuid: "00000000-0000-4000-8000-00000000010f", - result: "Wake result with no model turns.", + result: "", numTurns: 0, origin: { kind: "task-notification" }, }), diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index c84aff8913c8f..d51b3f23bf101 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -8877,7 +8877,22 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( return next; }); } - const lastResult = resultMessages.at(-1); + // The CLI can append empty session-task lifecycle successes to + // a failed wake before this continuation starts. Those frames + // must not replace the result that explains why the turn ended. + // An all-empty wake still needs its final result to settle. + const lastResult = + resultMessages.findLast( + (entry) => + !( + isClaudeTaskNotificationOriginResult(entry) && + entry.subtype === "success" && + !entry.is_error && + entry.num_turns === 0 && + entry.result.length === 0 && + terminalStatusFromResult(entry) === "completed" + ), + ) ?? resultMessages.at(-1); if (lastResult !== undefined) { yield* handleSdkMessage({ query: querySession.query, message: lastResult }); return;