diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index 95432c40e6887..3139da0a45fff 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -15585,6 +15585,66 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); + it.effect( + "does not count an empty task-notification success buffered while idle as background work", + () => + Effect.scoped( + Effect.gen(function* () { + const harness = yield* makeWakeHarness; + const now = yield* DateTime.now; + yield* harness.runtime.startTurn( + makeClaudeTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now, + attemptId: RunAttemptId.make("attempt-claude-idle-empty-notification"), + text: "Wait in the background.", + attachments: [], + }), + ); + yield* Queue.offer(harness.sdkMessages, turnOneResult); + yield* harness.awaitTerminal(1); + + // On resume after a server restart, the CLI reports the shell the old process lost, + // then closes that notification with an empty zero-turn success. + yield* harness.offerAndWait( + claudeSdkFrame({ + type: "system", + subtype: "task_notification", + task_id: "task-lost-shell", + tool_use_id: "toolu-lost-shell", + status: "stopped", + output_file: "", + summary: "Background shell command didn't finish before the previous session ended", + uuid: "00000000-0000-4000-8000-000000000a01", + session_id: WAKE_NATIVE_SESSION, + }), + ); + yield* harness.offerAndWait(wakeTurnInit); + yield* harness.offerAndWait( + makeResultFrame({ + uuid: "00000000-0000-4000-8000-000000000a02", + result: "", + numTurns: 0, + origin: { kind: "task-notification" }, + terminalReason: null, + }), + ); + assert.lengthOf(harness.continuationRequests, 1); + assert.isFalse(yield* harness.hasPendingBackgroundWork); + }).pipe( + Effect.provide( + Layer.mergeAll( + IdAllocator.layer, + McpProviderSessions.layer, + NodeServices.layer, + NodeCrypto.layer, + ), + ), + ), + ), + ); + it.effect( "emits one interrupted terminal for a positive task-notification result racing interrupt", () => diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 3e10e09906796..c1ebd3be73f9f 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -13038,7 +13038,7 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* ( (message) => message.type === "user" || message.type === "assistant" || - message.type === "result", + (message.type === "result" && !isClaudeEmptyTaskNotificationSuccess(message)), ) ) { return true;