From 36509ef0516e278c4a1b28ca09809e2159b81485 Mon Sep 17 00:00:00 2001 From: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:24:22 -0700 Subject: [PATCH 1/2] fix(server): Claude monitors show as monitors, not commands Claude Code runs its Monitor tool as a local_bash task, the same task_type as a background Bash command, so the Waiting roster and the wake summary called every Claude monitor a command. The adapter now remembers Monitor tool calls and types the task they start as a monitor. Recorded live as the claude_background_monitor_wake replay fixture. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../record-claude-agent-sdk-replay-fixture.ts | 8 ++ .../Adapters/ClaudeAdapterV2.ts | 58 +++++++++++-- .../claude_transcript.ndjson | 87 +++++++++++++++++++ .../claude_background_monitor_wake/input.ts | 29 +++++++ .../claude_background_monitor_wake/output.ts | 67 ++++++++++++++ .../testkit/fixtures/index.ts | 18 ++++ 6 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/claude_transcript.ndjson create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/input.ts create mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/output.ts diff --git a/apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts b/apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts index 1e65169a6522..2d5e348a35b7 100644 --- a/apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts +++ b/apps/server/scripts/record-claude-agent-sdk-replay-fixture.ts @@ -67,6 +67,7 @@ import { CLAUDE_BACKGROUND_SUBAGENT_LIFECYCLE_RESUME_PROMPT, CLAUDE_BACKGROUND_SUBAGENT_LIFECYCLE_STOP_PROMPT, } from "../src/orchestration-v2/testkit/fixtures/claude_background_subagent_lifecycle/input.ts"; +import { CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT } from "../src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/input.ts"; import { CLAUDE_BACKGROUND_TASK_INTERRUPT_PROMPT } from "../src/orchestration-v2/testkit/fixtures/claude_background_task_interrupt/input.ts"; import { CLAUDE_BACKGROUND_WAKE_BEFORE_QUEUED_PROMPT_LAUNCH_PROMPT } from "../src/orchestration-v2/testkit/fixtures/claude_background_wake_before_queued_prompt/input.ts"; import { @@ -192,6 +193,13 @@ const CLAUDE_RECORDINGS = { enableTools: true, backgroundWakeCounts: [1, 0], }, + claude_background_monitor_wake: { + prompts: [CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT], + defaultTranscriptFile: "fixtures/claude_background_monitor_wake/claude_transcript.ndjson", + queryMode: "streaming", + enableTools: true, + backgroundWakeCounts: [1], + }, claude_background_subagent_lifecycle: { prompts: [ CLAUDE_BACKGROUND_SUBAGENT_LIFECYCLE_LAUNCH_PROMPT, diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 18c354638385..3a2ff0fc88a7 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -1576,6 +1576,7 @@ const CLAUDE_KNOWN_TOOL_CLASSIFICATIONS: Record< glob: { itemType: "dynamic_tool", requestKind: "file-read" }, grep: { itemType: "dynamic_tool", requestKind: "file-read" }, ls: { itemType: "dynamic_tool", requestKind: "file-read" }, + monitor: { itemType: "dynamic_tool", requestKind: "command" }, multiedit: { itemType: "file_change", requestKind: "file-change" }, notebookedit: { itemType: "file_change", requestKind: "file-change" }, read: { itemType: "dynamic_tool", requestKind: "file-read" }, @@ -1700,14 +1701,18 @@ function isClaudeOpaqueBackgroundTaskType(taskType: string | null | undefined): function claudePendingBackgroundTask(input: { readonly taskId: string; readonly taskType: string | null; + // Claude runs a Monitor as a local_bash task, so only the Monitor tool call + // that started it tells it apart from a background Bash command. + readonly startedByMonitor: boolean; readonly description: string | undefined; }): OrchestrationV2PendingBackgroundTask { return { taskId: input.taskId, - kind: - (input.taskType === null - ? undefined - : CLAUDE_OPAQUE_BACKGROUND_TASK_KINDS.get(input.taskType)) ?? "background_task", + kind: input.startedByMonitor + ? "monitor" + : ((input.taskType === null + ? undefined + : CLAUDE_OPAQUE_BACKGROUND_TASK_KINDS.get(input.taskType)) ?? "background_task"), ...(input.description !== undefined && input.description.trim().length > 0 ? { description: input.description } : {}), @@ -1742,6 +1747,7 @@ function claudePendingBackgroundTasksFromRoster( function parseClaudeBackgroundTaskEntry( entry: unknown, + monitorTaskIds: ReadonlySet, ): OrchestrationV2PendingBackgroundTask | null { if (entry === null || typeof entry !== "object") { return null; @@ -1761,6 +1767,7 @@ function parseClaudeBackgroundTaskEntry( return claudePendingBackgroundTask({ taskId, taskType, + startedByMonitor: monitorTaskIds.has(taskId), description: typeof description === "string" ? description : undefined, }); } @@ -2944,6 +2951,42 @@ export function makeClaudeAdapterV2( const lastKnownOpaqueTasks = yield* Ref.make( new Map(), ); + // Monitor tool calls, and the tasks they started. A roster snapshot + // names only the task, so its kind comes from the task id. Entries + // are never removed, so both sets keep only the newest 64 ids. + const claudeMonitors = yield* Ref.make<{ + readonly toolUseIds: ReadonlySet; + readonly taskIds: ReadonlySet; + }>({ toolUseIds: new Set(), taskIds: new Set() }); + const boundedAdd = (ids: ReadonlySet, id: string): ReadonlySet => { + if (ids.has(id)) return ids; + const updated = new Set(ids).add(id); + for (const oldest of updated) { + if (updated.size <= 64) break; + updated.delete(oldest); + } + return updated; + }; + const rememberClaudeMonitorToolUses = (message: SDKMessage) => { + const monitorToolUseIds = claudeToolUseBlocksFromAssistantMessage(message).flatMap( + (toolUse) => (toolUse.name === "Monitor" ? [toolUse.id] : []), + ); + return monitorToolUseIds.length === 0 + ? Effect.void + : Ref.update(claudeMonitors, (current) => ({ + ...current, + toolUseIds: monitorToolUseIds.reduce(boundedAdd, current.toolUseIds), + })); + }; + /** True when a Monitor call started this task; remembers the task id. */ + const isClaudeMonitorTask = (taskId: string, toolUseId: string | undefined) => + Ref.modify(claudeMonitors, (current) => { + if (current.taskIds.has(taskId)) return [true, current] as const; + if (toolUseId === undefined || !current.toolUseIds.has(toolUseId)) { + return [false, current] as const; + } + return [true, { ...current, taskIds: boundedAdd(current.taskIds, taskId) }] as const; + }); const claudeTaskOutcome = (status: "completed" | "failed" | "stopped") => status === "completed" ? "completed" : status === "stopped" ? "cancelled" : "failed"; // Reads the roster, so call it before the notification clears the task from it. @@ -4969,8 +5012,9 @@ export function makeClaudeAdapterV2( return false; } const nextTasks: OrchestrationV2PendingBackgroundTask[] = []; + const { taskIds: monitorTaskIds } = yield* Ref.get(claudeMonitors); for (const entry of roster) { - const task = parseClaudeBackgroundTaskEntry(entry); + const task = parseClaudeBackgroundTaskEntry(entry, monitorTaskIds); if (task !== null) { nextTasks.push(task); } @@ -4992,6 +5036,7 @@ export function makeClaudeAdapterV2( claudePendingBackgroundTask({ taskId: message.task_id, taskType: claudeTaskTypeFromSdkMessage(message), + startedByMonitor: yield* isClaudeMonitorTask(message.task_id, message.tool_use_id), description: typeof message.description === "string" ? message.description : undefined, }), @@ -5054,6 +5099,9 @@ export function makeClaudeAdapterV2( } const message = input.message; + // Before any routing: a Monitor started during an idle wake turn + // reports its task before the drain replays the tool call. + yield* rememberClaudeMonitorToolUses(message); if (message.type === "rate_limit_event") { const rateLimitInfo = message.rate_limit_info; if (!rateLimitInfo) return; diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/claude_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/claude_transcript.ndjson new file mode 100644 index 000000000000..afc19f75dc29 --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/claude_transcript.ndjson @@ -0,0 +1,87 @@ +{"type":"transcript_start","provider":"claudeAgent","protocol":"claude-agent-sdk.query","version":"0.2.111","scenario":"claude_background_monitor_wake","metadata":{"prompts":["Live-test a background Monitor wake. You must call the Monitor tool before replying. Do exactly this, with no extra steps.\n\n1) Call the Monitor tool with description \"Background monitor test\" and this exact command:\n sleep 8 && echo MONITOR_DONE\n2) After that tool call returns, reply with exactly STARTED and stop. Do not poll, read its output, or wait for it.\n3) When its completion is reported later, reply with exactly WAKE_DONE and stop."],"model":"claude-sonnet-4-6","nativeSessionId":"64676197-7fbc-467e-aeac-eae812bfce63","queryMode":"streaming","tools":"claude_code","permissionMode":"bypassPermissions","backgroundWakeCounts":[1],"generatedBy":"recordClaudeAgentSdkReplayTranscript"}} +{"type":"expect_outbound","label":"query.open","frame":{"type":"query.open","options":{"model":"claude-sonnet-4-6","tools":{"type":"preset","preset":"claude_code"},"permissionMode":"bypassPermissions","settings":{"showThinkingSummaries":true},"allowDangerouslySkipPermissions":true,"sessionId":"64676197-7fbc-467e-aeac-eae812bfce63"}}} +{"type":"expect_outbound","label":"prompt.offer:1","frame":{"type":"prompt.offer","message":{"type":"user","message":{"role":"user","content":"Live-test a background Monitor wake. You must call the Monitor tool before replying. Do exactly this, with no extra steps.\n\n1) Call the Monitor tool with description \"Background monitor test\" and this exact command:\n sleep 8 && echo MONITOR_DONE\n2) After that tool call returns, reply with exactly STARTED and stop. Do not poll, read its output, or wait for it.\n3) When its completion is reported later, reply with exactly WAKE_DONE and stop."},"parent_tool_use_id":null,"uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}}} +{"type":"emit_inbound","label":"command_lifecycle","frame":{"type":"command_lifecycle","command_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","state":"queued","uuid":"91ea2e3b-de6f-4e62-b4a6-94b3eba0220b","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"command_lifecycle","frame":{"type":"command_lifecycle","command_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","state":"started","uuid":"e32ce64d-dd44-4696-b909-7747289aad48","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.281","cwd":"/tmp/claude-replay-claude_background_monitor_wake","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"8e0a0969-84d9-4916-a1ae-be7c94d00a4e","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"status","status":"requesting","session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"eaed9ca9-30d4-4601-bbcd-892e7ffa7861"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_start","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm3DeutyTGV52dHs6F9","type":"message","role":"assistant","content":[],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":36734,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36734},"output_tokens":8,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"69c69399-077c-4cc1-82a7-65f2189d3a8c","ttft_ms":1833,"user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","user_message_uuids":["adf55c08-889c-4e86-8d93-06fc4b672108"]}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"f76e477c-2e5b-4209-b123-677a4af76296"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":1,"estimated_tokens_delta":1,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"2871a71c-af9e-4cab-9eda-4b1724e0c390","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"The","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"ce191612-c9cc-4e5b-b862-b17f24384aae"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":7,"estimated_tokens_delta":6,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"ef0de0f8-b03a-4e66-b128-129df44ecb6a","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" user wants me to call","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"9ce97625-d8dd-45e6-9280-be35f01ce97a"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":18,"estimated_tokens_delta":11,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"95510972-3f0a-4119-b4b7-8eb3a8048369","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the Monitor tool with a specific command,","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"abc9b993-70ff-48f4-9bfc-523bd2d103ed"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":21,"estimated_tokens_delta":3,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"3eebfb09-3a15-441e-a6f7-39fe29a239cf","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" then reply","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"86d140d5-7dd2-485a-bbd8-35d4e88ae053"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":23,"estimated_tokens_delta":2,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"488cef41-737d-44d1-a077-07515d6589f3","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" with \"","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"1896e267-0ab6-4c3a-8d93-5dfb79b72888"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":26,"estimated_tokens_delta":3,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"28df4f7f-e3ff-4f36-a71e-967bf13f996c","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"STARTED\",","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"1adc5301-d813-4c84-9f71-6fbac93e6e42"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":27,"estimated_tokens_delta":1,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"5475b979-a8d6-4e83-b9a8-6cbb2b4f1fb8","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" and","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"d6a497be-0620-48d0-9359-97852086bab7"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":29,"estimated_tokens_delta":2,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"feda5fe3-3ae1-4bbc-ad99-d0a13838d2b1","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" later","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"1e6d217d-b377-4de4-a7cc-676b95f2b9da"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":31,"estimated_tokens_delta":2,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"beadacd0-e60d-4c94-82fd-856235f1a2fc","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" when","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"7644d5ab-da71-45d6-9b5d-ae8857c10fc7"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":35,"estimated_tokens_delta":4,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"6fecbc2a-da03-4c14-82fd-c448ffe526e0","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" the completion","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"79bdd9bf-3ad0-41a5-bd2b-41256cf3ce13"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":43,"estimated_tokens_delta":8,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"f301b71a-3c31-4c47-a8ff-70eb8f356499","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":" is reported, reply with \"WAKE","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"17ecd6ad-2588-415e-a12f-3f139e0c1b4f"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":45,"estimated_tokens_delta":2,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"5ea9679a-06d7-4da4-b3c2-efe073977449","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"_DONE\".","estimated_tokens":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"29c3b50c-f8aa-4565-bc15-6222f4237f4c"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"thinking_tokens","estimated_tokens":110,"estimated_tokens_delta":65,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"9ef59e10-dcc5-4102-83fc-aee26de50dce","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"ErADCqoBCBIYAipAMgHUXoDHYZX2We+WqJPeiY0P/pfdyYITiNobZJMp05jAq3IwlOKZJFIZ1eA1RM8lm9V2djkTBTy0xJoc1T2uIzIRY2xhdWRlLXNvbm5ldC00LTY4AEIIdGhpbmtpbmdaJGIyYmZmMDQ4LWRjZGMtNDJlZS05MDA2LWUyZGU1ZWEyN2ZhN3IQKYRrdYIR6zHmteXVTg5ZsIgBAagB44Ps1QawAQISDOBAWFRm3Hcmq6TGWxoMnauP+lqwKH63EYTBIjCqgpqPeUm5mfXbB77SD8plDusXjw/jnza/WVx6nzM38pOTDCu0LDXwr5ynwABkL8oqsgGat9DSheb2dLBiVRpqR9bzz8tEP3ohKrjsOxttGVsGBszEAYHppGwHl039yKn7i0zCIJU98rCUCPiSbWk8hqqf40ToEWr7jDteTB9LQbZTwY7F1KXHtJpxKks56LOuU8UXpkU3vnRRgMKgrrzZb9KUr5g5nw8cHyrbJPTspTzkOGMcSdnC6unCTlFbyGeHYX74hJnKHbhva7YiSTdOiW6tMZS+meSwXYpUw4w3i5wDiGCrGAE="}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"df70b18d-b075-4430-b552-5a8c2b098617"}} +{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm3DeutyTGV52dHs6F9","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"The user wants me to call the Monitor tool with a specific command, then reply with \"STARTED\", and later when the completion is reported, reply with \"WAKE_DONE\".","signature":"ErADCqoBCBIYAipAMgHUXoDHYZX2We+WqJPeiY0P/pfdyYITiNobZJMp05jAq3IwlOKZJFIZ1eA1RM8lm9V2djkTBTy0xJoc1T2uIzIRY2xhdWRlLXNvbm5ldC00LTY4AEIIdGhpbmtpbmdaJGIyYmZmMDQ4LWRjZGMtNDJlZS05MDA2LWUyZGU1ZWEyN2ZhN3IQKYRrdYIR6zHmteXVTg5ZsIgBAagB44Ps1QawAQISDOBAWFRm3Hcmq6TGWxoMnauP+lqwKH63EYTBIjCqgpqPeUm5mfXbB77SD8plDusXjw/jnza/WVx6nzM38pOTDCu0LDXwr5ynwABkL8oqsgGat9DSheb2dLBiVRpqR9bzz8tEP3ohKrjsOxttGVsGBszEAYHppGwHl039yKn7i0zCIJU98rCUCPiSbWk8hqqf40ToEWr7jDteTB9LQbZTwY7F1KXHtJpxKks56LOuU8UXpkU3vnRRgMKgrrzZb9KUr5g5nw8cHyrbJPTspTzkOGMcSdnC6unCTlFbyGeHYX74hJnKHbhva7YiSTdOiW6tMZS+meSwXYpUw4w3i5wDiGCrGAE="}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":36734,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36734},"output_tokens":8,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"72df525d-d059-4e64-9d8d-5eda8b99bf76","timestamp":"2026-09-29T00:10:11.318Z","user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","user_message_uuids":["adf55c08-889c-4e86-8d93-06fc4b672108"]}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_stop","index":0},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"8472f39c-2233-4af7-8d9a-d5e588307776"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_019b4EUbhEcYP4TS7vZn27N1","name":"Monitor","input":{},"caller":{"type":"direct"}}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"fdeffd2f-8613-44ff-8fe1-feeba50fc256"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"1f6fc1ad-9211-4bfa-b598-92dbb56d47d2"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"descr"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"f6c4f9cd-7fcc-4eca-b9c7-da41e2cde3e2"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"iption\""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"eb3d34be-a3b3-4b0f-92b4-4b5a9012e1ae"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":": \"Back"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"b7e94cd7-d9f3-467e-8dfc-98fe9d133c03"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"ground "}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"1e781433-b895-426a-bbc3-234b6cb443cf"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"monitor"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"b7a49a36-85c8-445c-949b-2e69aaf99ed2"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" test\""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"d8c1eefb-2566-4ca2-a48b-71825d2a7fa2"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":", \"comm"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"08d53ab7-98fb-41b2-a75c-cb12778c0831"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"and\": \""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"efbefbcc-0a97-4688-9e75-34cac23af5de"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"sleep 8"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"00027ab6-8d17-4c1e-b734-620754f4b897"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" && ech"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"54a3fda5-3567-48a6-9a8b-b2923c472f1f"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"o MONIT"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"efffeee1-1f93-475b-8635-3ff2cf44f5ff"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"OR_DONE"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"8ebe8bf6-6086-42d0-9fe8-52ad36d712ed"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"000ffc35-c868-4889-aa25-3d492d9fc6db"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":", \"time"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"3731cb8d-9883-4845-8f40-9f7451b8a656"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"out_ms\""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"4fc147a3-52ca-42da-811f-643ee6f02331"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":": 30000}"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"3709184f-2e3b-4f32-8a3a-abac472bf6aa"}} +{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm3DeutyTGV52dHs6F9","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_019b4EUbhEcYP4TS7vZn27N1","name":"Monitor","input":{"description":"Background monitor test","command":"sleep 8 && echo MONITOR_DONE","timeout_ms":30000},"caller":{"type":"direct"}}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":36734,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36734},"output_tokens":8,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"7a287f0f-1173-4090-a251-a2b6daa36643","timestamp":"2026-09-29T00:10:11.862Z","wire_tool_inputs":{"toolu_019b4EUbhEcYP4TS7vZn27N1":{"description":"Background monitor test","command":"sleep 8 && echo MONITOR_DONE","timeout_ms":30000}}}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_stop","index":1},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"96b3534c-55d4-4296-9540-dc27616b1543"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"container":null},"usage":{"input_tokens":3,"cache_creation_input_tokens":36734,"cache_read_input_tokens":0,"output_tokens":162,"output_tokens_details":{"thinking_tokens":51},"iterations":[{"input_tokens":3,"output_tokens":162,"cache_read_input_tokens":0,"cache_creation_input_tokens":36734,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":36734},"type":"message"}]},"context_management":{"applied_edits":[]}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"4eb5756f-2c5a-46b1-9517-c16b70e94b68"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_stop"},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"9043a2b3-a600-4f98-816e-74402bde2500"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"background_tasks_changed","tasks":[{"task_id":"b1htjkjev","task_type":"local_bash","description":"Background monitor test"}],"uuid":"cf8656bd-e25a-4c77-92ca-534c406ffa75","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"task_started","task_id":"b1htjkjev","tool_use_id":"toolu_019b4EUbhEcYP4TS7vZn27N1","description":"Background monitor test","is_backgrounded":true,"task_type":"local_bash","uuid":"32b719dc-9b94-4025-8111-a457f554d9b8","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"user","frame":{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_019b4EUbhEcYP4TS7vZn27N1","type":"tool_result","content":"Monitor started (task b1htjkjev, expires in 30s unless the source ends first; you get one notice at expiry — re-arm if you still need the watch). You will be notified on each event. Keep working — do not poll or sleep. Events may arrive while you are waiting for the user — an event is not their reply."}]},"parent_tool_use_id":null,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"f15f3574-f0ad-4715-9870-11687bdc752b","timestamp":"2026-09-29T00:10:11.947Z","tool_use_result":{"taskId":"b1htjkjev","timeoutMs":30000,"persistent":false}}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"status","status":"requesting","session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"39356434-1f31-4075-b2e1-55af474ace8d"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_start","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm3SRchuWCwq3DM44Vm","type":"message","role":"assistant","content":[],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":251,"cache_read_input_tokens":36734,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":251},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"dc0fdb19-1a98-4495-aa15-5c58badd4e15","ttft_ms":1202}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"6be6f7f5-a105-40a2-aa4c-40c0f3a3e1e2"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"STARTED"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"cb9b3f01-6573-4a04-8f17-db382b96fce7"}} +{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm3SRchuWCwq3DM44Vm","type":"message","role":"assistant","content":[{"type":"text","text":"STARTED"}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":251,"cache_read_input_tokens":36734,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":251},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"ccc75f1c-d49e-4fd6-bc57-0dded2957c34","timestamp":"2026-09-29T00:10:13.244Z"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_stop","index":0},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"68394f2d-406f-4f09-9f83-d8213968e955"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"container":null},"usage":{"input_tokens":1,"cache_creation_input_tokens":251,"cache_read_input_tokens":36734,"output_tokens":5,"output_tokens_details":{"thinking_tokens":0},"iterations":[{"input_tokens":1,"output_tokens":5,"cache_read_input_tokens":36734,"cache_creation_input_tokens":251,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":251},"type":"message"}]},"context_management":{"applied_edits":[]}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"9a5bcc91-9951-444c-a64b-1a4ccfa3e4a0"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_stop"},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"804b573a-17bf-4561-a942-fee958dcdce0"}} +{"type":"emit_inbound","label":"result","frame":{"duration_api_ms":5733,"stop_reason":"end_turn","session_id":"64676197-7fbc-467e-aeac-eae812bfce63","total_cost_usd":0.24230520000000003,"usage":{"input_tokens":4,"cache_creation_input_tokens":36985,"cache_read_input_tokens":36734,"output_tokens":167,"output_tokens_details":{"thinking_tokens":51},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":36985,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":1,"output_tokens":5,"cache_read_input_tokens":36734,"cache_creation_input_tokens":251,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":251},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-sonnet-4-6":{"inputTokens":7,"outputTokens":178,"cacheReadInputTokens":36734,"cacheCreationInputTokens":38099,"webSearchRequests":0,"costUSD":0.24230520000000003,"contextWindow":200000,"maxOutputTokens":32000,"thinkingTokens":51,"canonicalModel":"claude-sonnet-4-6","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"is_error":false,"num_turns":2,"subtype":"success","api_error_status":null,"result":"STARTED","ttft_ms":2986,"type":"result","duration_ms":4959,"uuid":"12f57b0a-1c88-47f3-8aae-05da2f76d42e","ttft_stream_ms":2039,"time_to_request_ms":207,"first_content_frame_ms":2040,"user_message_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","user_message_uuids":["adf55c08-889c-4e86-8d93-06fc4b672108"],"request_sent_wall_ms":1790640608539,"queued_turn_count":0,"result_index":0}} +{"type":"emit_inbound","label":"command_lifecycle","frame":{"type":"command_lifecycle","command_uuid":"adf55c08-889c-4e86-8d93-06fc4b672108","state":"completed","uuid":"1d5c7457-d238-442c-b1ba-4450c0646e29","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"background_tasks_changed","tasks":[],"uuid":"c83009b1-7329-4295-8c2a-7a66675b5888","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"task_updated","task_id":"b1htjkjev","patch":{"status":"completed","end_time":1790640619951},"uuid":"da0ee248-aa7a-4045-a78d-43b10dea0047","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"task_notification","task_id":"b1htjkjev","tool_use_id":"toolu_019b4EUbhEcYP4TS7vZn27N1","status":"completed","output_file":"/tmp/claude-replay/64676197-7fbc-467e-aeac-eae812bfce63/tasks/b1htjkjev.output","summary":"Monitor \"Background monitor test\" stream ended","uuid":"9b639a75-eb5e-4086-b594-33797c8e2558","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"init","agents":[],"apiKeySource":"none","claude_code_version":"2.1.281","cwd":"/tmp/claude-replay-claude_background_monitor_wake","tools":[],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"fast_mode_state":"off","uuid":"d0a3bf5c-ad8c-495d-aeb9-0e2496b41e17","session_id":"64676197-7fbc-467e-aeac-eae812bfce63"}} +{"type":"emit_inbound","label":"system","frame":{"type":"system","subtype":"status","status":"requesting","session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"23a0f4a5-3f93-4862-881d-927e57c703a8"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_start","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm433UmJY5WPhmFxGzR","type":"message","role":"assistant","content":[],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":325,"cache_read_input_tokens":36985,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":325},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"b2fec19c-fa97-4899-a9b7-0a3ff5661f63","ttft_ms":1697}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"a0a352ce-da54-49e3-ad11-96cee06f9f77"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"WAKE"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"b36f7e81-5f2a-466e-a8e0-eb8db15790e8"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"_DONE"}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"9eaa4f54-18bc-4b00-b3db-469751d1e231"}} +{"type":"emit_inbound","label":"assistant","frame":{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_011CfWm433UmJY5WPhmFxGzR","type":"message","role":"assistant","content":[{"type":"text","text":"WAKE_DONE"}],"container":null,"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":325,"cache_read_input_tokens":36985,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":325},"output_tokens":2,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","uuid":"be9cefe3-e008-46fa-854b-09b486ebe3db","timestamp":"2026-09-29T00:10:21.867Z"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"content_block_stop","index":0},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"288f6f2f-e84d-4b98-8f2e-0df99d2f6507"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"container":null},"usage":{"input_tokens":3,"cache_creation_input_tokens":325,"cache_read_input_tokens":36985,"output_tokens":8,"output_tokens_details":{"thinking_tokens":0},"iterations":[{"input_tokens":3,"output_tokens":8,"cache_read_input_tokens":36985,"cache_creation_input_tokens":325,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":325},"type":"message"}]},"context_management":{"applied_edits":[]}},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"933f750f-2fac-4b19-95cd-8744481065e3"}} +{"type":"emit_inbound","label":"stream_event","frame":{"type":"stream_event","event":{"type":"message_stop"},"session_id":"64676197-7fbc-467e-aeac-eae812bfce63","parent_tool_use_id":null,"uuid":"18dba4eb-2cc5-4177-bdb4-a966718113e0"}} +{"type":"emit_inbound","label":"result:background-wake:1","frame":{"duration_api_ms":7541,"stop_reason":"end_turn","session_id":"64676197-7fbc-467e-aeac-eae812bfce63","total_cost_usd":0.25547970000000003,"usage":{"input_tokens":3,"cache_creation_input_tokens":325,"cache_read_input_tokens":36985,"output_tokens":8,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":325,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":3,"output_tokens":8,"cache_read_input_tokens":36985,"cache_creation_input_tokens":325,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":325},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-sonnet-4-6":{"inputTokens":10,"outputTokens":186,"cacheReadInputTokens":73719,"cacheCreationInputTokens":38424,"webSearchRequests":0,"costUSD":0.25547970000000003,"contextWindow":200000,"maxOutputTokens":32000,"thinkingTokens":51,"canonicalModel":"claude-sonnet-4-6","provider":"firstParty","costBasis":"list"}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","origin":{"kind":"task-notification"},"subagent_stats":{"spawned":0,"requested":{"background":0,"foreground":0,"unset":0},"started_in_background":0,"max_depth":0,"spawned_by_subagents":0,"completed":0,"failed":0,"killed":{"parent":0,"user":0,"system":0},"refused":{"depth_limit":0,"concurrency_limit":0,"budget":0},"by_type":{}},"is_error":false,"num_turns":1,"subtype":"success","api_error_status":null,"result":"WAKE_DONE","ttft_ms":1816,"type":"result","duration_ms":1835,"uuid":"6e2f21cd-796f-4fd5-8d77-8b7c68b6b89e","ttft_stream_ms":1721,"time_to_request_ms":24,"first_content_frame_ms":1721,"queued_turn_count":0,"result_index":1}} +{"type":"runtime_exit","status":"success"} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/input.ts b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/input.ts new file mode 100644 index 000000000000..6121fe40e303 --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/input.ts @@ -0,0 +1,29 @@ +import { claudeBackgroundWakeResultLabel } from "../../../Adapters/ClaudeAdapterV2.testkit.ts"; +import type { OrchestratorFixtureInput } from "../shared.ts"; + +export const CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT = [ + "Live-test a background Monitor wake. You must call the Monitor tool before replying. Do exactly this, with no extra steps.", + "", + '1) Call the Monitor tool with description "Background monitor test" and this exact command:', + " sleep 8 && echo MONITOR_DONE", + "2) After that tool call returns, reply with exactly STARTED and stop. Do not poll, read its output, or wait for it.", + "3) When its completion is reported later, reply with exactly WAKE_DONE and stop.", +].join("\n"); + +// Same shape as claude_background_task_wake: the monitor's one event ends it, +// and that end wakes Claude after the root turn settled. +export function claudeBackgroundMonitorWakeInput(): OrchestratorFixtureInput { + return { + steps: [ + { type: "message", text: CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT }, + { + type: "await_run_status", + targetRunIndex: 2, + status: "running", + waitForTurnItemType: "assistant_message", + }, + { type: "release_replay_gate", label: claudeBackgroundWakeResultLabel(1) }, + { type: "await_run_status", targetRunIndex: 2, status: "completed" }, + ], + }; +} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/output.ts b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/output.ts new file mode 100644 index 000000000000..eea7b7e4ac06 --- /dev/null +++ b/apps/server/src/orchestration-v2/testkit/fixtures/claude_background_monitor_wake/output.ts @@ -0,0 +1,67 @@ +import { assert } from "@effect/vitest"; +import type { ProviderReplayTranscript } from "@t3tools/contracts"; + +import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; +import { + assertBaseProjection, + assertSemanticProjectionIntegrity, + assertUserMessagesInclude, + backgroundNotifications, + projectionFor, +} from "../shared.ts"; +import { CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT } from "./input.ts"; + +const MONITOR_TASK_ID = "b1htjkjev"; + +// Claude reports a Monitor as task_type local_bash, the same as a background +// Bash command. Only the Monitor tool call that started the task tells them +// apart, so the roster and the wake must name it a monitor. +export function assertClaudeBackgroundMonitorWakeOutput( + result: OrchestratorV2ScenarioResult, + transcript: ProviderReplayTranscript, +) { + assertBaseProjection({ + result, + transcript, + runCount: 2, + runStatuses: ["completed", "completed"], + }); + const projection = projectionFor(result, transcript.scenario); + assertSemanticProjectionIntegrity(projection); + assertUserMessagesInclude(projection, [CLAUDE_BACKGROUND_MONITOR_WAKE_PROMPT]); + + const rosterTasks = result.domainEvents + .flatMap((event) => + event.type === "provider-thread.updated" ? (event.payload.pendingBackgroundTasks ?? []) : [], + ) + .filter((task) => task.taskId === MONITOR_TASK_ID); + // Claude's first roster snapshot lands one frame before the task_started + // that links the task to its Monitor call, so only that entry reads command. + assert.isAbove(rosterTasks.length, 1); + for (const task of rosterTasks.slice(1)) { + assert.deepEqual(task, { + taskId: MONITOR_TASK_ID, + description: "Background monitor test", + kind: "monitor", + }); + } + assert.deepEqual(projection.providerThreads[0]?.pendingBackgroundTasks ?? [], []); + + assert.deepEqual(backgroundNotifications(projection), [ + { + summary: 'Monitor "Background monitor test" finished', + outcome: "completed", + source: { kind: "monitor" }, + }, + ]); + + const [rootRun, wakeRun] = projection.runs; + const assistantTexts = (runId: string | undefined) => + projection.turnItems.flatMap((item) => + item.runId === runId && item.type === "assistant_message" ? [item.text.trim()] : [], + ); + assert.deepEqual(assistantTexts(rootRun?.id), ["STARTED"]); + assert.deepEqual(assistantTexts(wakeRun?.id), ["WAKE_DONE"]); + + assert.lengthOf(projection.subagents, 0); +} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts index 74785f02eacd..1694692faa03 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts +++ b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts @@ -12,6 +12,8 @@ import { assertClaudeBackgroundWakeBeforeQueuedPromptOutput } from "./claude_bac import { claudeBackgroundWakeBeforeQueuedPromptNoEchoInput } from "./claude_background_wake_before_queued_prompt_no_echo/input.ts"; import { assertClaudeBackgroundWakeBeforeQueuedPromptNoEchoOutput } from "./claude_background_wake_before_queued_prompt_no_echo/output.ts"; import { assertClaudeBackgroundTaskInterruptOutput } from "./claude_background_task_interrupt/output.ts"; +import { claudeBackgroundMonitorWakeInput } from "./claude_background_monitor_wake/input.ts"; +import { assertClaudeBackgroundMonitorWakeOutput } from "./claude_background_monitor_wake/output.ts"; import { claudeBackgroundTaskWakeInput } from "./claude_background_task_wake/input.ts"; import { assertClaudeBackgroundTaskWakeOutput } from "./claude_background_task_wake/output.ts"; import { claudeIdleResumeInput } from "./claude_idle_resume/input.ts"; @@ -243,6 +245,22 @@ export const ORCHESTRATOR_REPLAY_FIXTURES: ReadonlyArray Date: Mon, 28 Sep 2026 18:15:02 -0700 Subject: [PATCH 2/2] fix(server): a running Claude monitor stays a monitor however many start after it The first version capped remembered Monitor ids at the newest 64, so a long-running monitor flipped back to "command" once 64 newer monitors had started. Monitor calls are now forgotten when their task_started links them to a task or their tool_result returns without one, and monitor tasks are forgotten when they leave their thread's roster, their task_notification arrives, or their thread's roster is dropped. Nothing live is ever evicted. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Adapters/ClaudeAdapterV2.test.ts | 135 ++++++++++++++++++ .../Adapters/ClaudeAdapterV2.ts | 131 +++++++++++------ 2 files changed, 224 insertions(+), 42 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts index b3a6d4a09232..2ef8fcc0d7c4 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts @@ -3308,6 +3308,141 @@ describe("ClaudeAdapterV2 background wake turns", () => { ), ); + // Frame shapes follow the claude_background_monitor_wake recording: Claude + // runs a Monitor as a local_bash task, linked to its call by tool_use_id. + it.effect("keeps a running Claude monitor typed after many newer monitors end", () => + Effect.scoped( + Effect.gen(function* () { + const harness = yield* makeWakeHarness; + const now = yield* DateTime.now; + let frameNumber = 0; + const nextUuid = () => `00000000-0000-4000-8000-${String(++frameNumber).padStart(12, "0")}`; + const monitorFrames = (index: number) => { + const taskId = `monitor-task-${index}`; + const toolUseId = `toolu_monitor_${index}`; + const description = `Monitor ${index}`; + return { + taskId, + start: [ + claudeSdkFrame({ + type: "assistant", + message: { + model: "claude-sonnet-4-6", + id: `msg_monitor_${index}`, + type: "message", + role: "assistant", + content: [ + { + type: "tool_use", + id: toolUseId, + name: "Monitor", + input: { description, command: "sleep 8 && echo MONITOR_DONE" }, + }, + ], + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 1, output_tokens: 1 }, + }, + parent_tool_use_id: null, + uuid: nextUuid(), + session_id: WAKE_NATIVE_SESSION, + }), + claudeSdkFrame({ + type: "system", + subtype: "task_started", + task_id: taskId, + tool_use_id: toolUseId, + description, + is_backgrounded: true, + task_type: "local_bash", + uuid: nextUuid(), + session_id: WAKE_NATIVE_SESSION, + }), + claudeSdkFrame({ + type: "user", + message: { + role: "user", + content: [ + { + type: "tool_result", + tool_use_id: toolUseId, + content: `Monitor started (task ${taskId}).`, + }, + ], + }, + parent_tool_use_id: null, + uuid: nextUuid(), + session_id: WAKE_NATIVE_SESSION, + tool_use_result: { taskId, persistent: false }, + }), + ], + end: claudeSdkFrame({ + type: "system", + subtype: "task_notification", + task_id: taskId, + tool_use_id: toolUseId, + status: "completed", + output_file: `/tmp/claude-replay/tasks/${taskId}.output`, + summary: `Monitor "${description}" stream ended`, + uuid: nextUuid(), + session_id: WAKE_NATIVE_SESSION, + }), + }; + }; + + yield* harness.runtime.startTurn( + makeClaudeTestTurnInput({ + threadId: harness.threadId, + providerThread: harness.providerThread, + now, + attemptId: RunAttemptId.make("attempt-claude-many-monitors"), + text: "Watch the deploy, then re-arm short watches.", + attachments: [], + }), + ); + const longRunning = monitorFrames(0); + for (const frame of longRunning.start) { + yield* Queue.offer(harness.sdkMessages, frame); + } + // More newer monitors start and end than any fixed id cap would hold. + for (let index = 1; index <= 65; index++) { + const monitor = monitorFrames(index); + for (const frame of monitor.start) { + yield* Queue.offer(harness.sdkMessages, frame); + } + yield* Queue.offer(harness.sdkMessages, monitor.end); + } + yield* Queue.offer( + harness.sdkMessages, + claudeSdkFrame({ + type: "system", + subtype: "background_tasks_changed", + tasks: [ + { + task_id: longRunning.taskId, + task_type: "local_bash", + description: "Monitor 0", + }, + ], + uuid: nextUuid(), + session_id: WAKE_NATIVE_SESSION, + }), + ); + yield* Queue.offer( + harness.sdkMessages, + makeResultFrame({ uuid: nextUuid(), result: "Watching." }), + ); + yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal"); + + const roster = providerThreadRosterEvents(harness.events).at(-1)?.providerThread + .pendingBackgroundTasks; + assert.deepEqual(roster, [ + { taskId: longRunning.taskId, kind: "monitor", description: "Monitor 0" }, + ]); + }).pipe(Effect.provide(Layer.merge(idAllocatorLayer, NodeServices.layer))), + ), + ); + it.effect("stops background work after the turn settled", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts index 3a2ff0fc88a7..68dda79bd821 100644 --- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts @@ -1747,7 +1747,7 @@ function claudePendingBackgroundTasksFromRoster( function parseClaudeBackgroundTaskEntry( entry: unknown, - monitorTaskIds: ReadonlySet, + monitorTasks: ReadonlyMap, ): OrchestrationV2PendingBackgroundTask | null { if (entry === null || typeof entry !== "object") { return null; @@ -1767,7 +1767,7 @@ function parseClaudeBackgroundTaskEntry( return claudePendingBackgroundTask({ taskId, taskType, - startedByMonitor: monitorTaskIds.has(taskId), + startedByMonitor: monitorTasks.has(taskId), description: typeof description === "string" ? description : undefined, }); } @@ -2951,41 +2951,67 @@ export function makeClaudeAdapterV2( const lastKnownOpaqueTasks = yield* Ref.make( new Map(), ); - // Monitor tool calls, and the tasks they started. A roster snapshot - // names only the task, so its kind comes from the task id. Entries - // are never removed, so both sets keep only the newest 64 ids. + // Live Claude monitors. A Monitor call waits in `calls` until its + // task_started links it to a task, or its tool_result ends the call + // without one. A task stays in `tasks` until it leaves its native + // thread's roster or its task_notification arrives, so a monitor is + // never forgotten while it runs. const claudeMonitors = yield* Ref.make<{ - readonly toolUseIds: ReadonlySet; - readonly taskIds: ReadonlySet; - }>({ toolUseIds: new Set(), taskIds: new Set() }); - const boundedAdd = (ids: ReadonlySet, id: string): ReadonlySet => { - if (ids.has(id)) return ids; - const updated = new Set(ids).add(id); - for (const oldest of updated) { - if (updated.size <= 64) break; - updated.delete(oldest); - } - return updated; - }; - const rememberClaudeMonitorToolUses = (message: SDKMessage) => { - const monitorToolUseIds = claudeToolUseBlocksFromAssistantMessage(message).flatMap( - (toolUse) => (toolUse.name === "Monitor" ? [toolUse.id] : []), + readonly calls: ReadonlySet; + readonly tasks: ReadonlyMap< + string, + { readonly toolUseId: string; readonly nativeThreadId: string } + >; + }>({ calls: new Set(), tasks: new Map() }); + const trackClaudeMonitorCalls = (message: SDKMessage) => { + const started = claudeToolUseBlocksFromAssistantMessage(message).flatMap((toolUse) => + toolUse.name === "Monitor" ? [toolUse.id] : [], ); - return monitorToolUseIds.length === 0 - ? Effect.void - : Ref.update(claudeMonitors, (current) => ({ - ...current, - toolUseIds: monitorToolUseIds.reduce(boundedAdd, current.toolUseIds), - })); + const returned = [ + ...claudeToolResultBlocksFromAssistantMessage(message), + ...claudeToolResultBlocksFromUserMessage(message), + ].map((toolResult) => toolResult.tool_use_id); + if (started.length === 0 && returned.length === 0) return Effect.void; + return Ref.update(claudeMonitors, (current) => { + // A replayed tool_use frame must not reopen a call whose task already started. + const linked = new Set([...current.tasks.values()].map((task) => task.toolUseId)); + const opened = started.filter((id) => !linked.has(id) && !current.calls.has(id)); + const closed = returned.filter((id) => current.calls.has(id)); + if (opened.length === 0 && closed.length === 0) return current; + const calls = new Set([...current.calls, ...opened]); + for (const id of closed) calls.delete(id); + return { ...current, calls }; + }); }; - /** True when a Monitor call started this task; remembers the task id. */ - const isClaudeMonitorTask = (taskId: string, toolUseId: string | undefined) => + /** True when a Monitor call started this task; links the task to that call. */ + const isClaudeMonitorTask = (input: { + readonly nativeThreadId: string; + readonly taskId: string; + readonly toolUseId: string | undefined; + }) => Ref.modify(claudeMonitors, (current) => { - if (current.taskIds.has(taskId)) return [true, current] as const; - if (toolUseId === undefined || !current.toolUseIds.has(toolUseId)) { + if (current.tasks.has(input.taskId)) return [true, current] as const; + if (input.toolUseId === undefined || !current.calls.has(input.toolUseId)) { return [false, current] as const; } - return [true, { ...current, taskIds: boundedAdd(current.taskIds, taskId) }] as const; + const calls = new Set(current.calls); + calls.delete(input.toolUseId); + const tasks = new Map(current.tasks).set(input.taskId, { + toolUseId: input.toolUseId, + nativeThreadId: input.nativeThreadId, + }); + return [true, { calls, tasks }] as const; + }); + /** Drops monitor tasks that ended: gone from the thread's roster, or notified. */ + const endClaudeMonitorTasks = ( + ended: (taskId: string, task: { readonly nativeThreadId: string }) => boolean, + ) => + Ref.modify(claudeMonitors, (current) => { + const endedIds = [...current.tasks].filter(([taskId, task]) => ended(taskId, task)); + if (endedIds.length === 0) return [current.tasks, current] as const; + const tasks = new Map(current.tasks); + for (const [taskId] of endedIds) tasks.delete(taskId); + return [tasks, { ...current, tasks }] as const; }); const claudeTaskOutcome = (status: "completed" | "failed" | "stopped") => status === "completed" ? "completed" : status === "stopped" ? "cancelled" : "failed"; @@ -3328,13 +3354,17 @@ export function makeClaudeAdapterV2( }); const clearPendingBackgroundTasksForNativeThread = (nativeThreadId: string) => - Ref.update(pendingBackgroundTasksByNativeThread, (current) => { - if (!current.has(nativeThreadId)) { - return current; - } - const updated = new Map(current); - updated.delete(nativeThreadId); - return updated; + Effect.gen(function* () { + yield* Ref.update(pendingBackgroundTasksByNativeThread, (current) => { + if (!current.has(nativeThreadId)) { + return current; + } + const updated = new Map(current); + updated.delete(nativeThreadId); + return updated; + }); + // The thread's process died or its turn failed; those monitors never notify. + yield* endClaudeMonitorTasks((_taskId, task) => task.nativeThreadId === nativeThreadId); }); // Drop idle wake traffic for a dead native process so it cannot pin @@ -5012,9 +5042,21 @@ export function makeClaudeAdapterV2( return false; } const nextTasks: OrchestrationV2PendingBackgroundTask[] = []; - const { taskIds: monitorTaskIds } = yield* Ref.get(claudeMonitors); + const listedTaskIds = new Set( + roster.flatMap((entry) => { + const taskId = + entry !== null && typeof entry === "object" + ? Reflect.get(entry, "task_id") + : undefined; + return typeof taskId === "string" ? [taskId] : []; + }), + ); + const monitorTasks = yield* endClaudeMonitorTasks( + (taskId, task) => + task.nativeThreadId === input.nativeThreadId && !listedTaskIds.has(taskId), + ); for (const entry of roster) { - const task = parseClaudeBackgroundTaskEntry(entry, monitorTaskIds); + const task = parseClaudeBackgroundTaskEntry(entry, monitorTasks); if (task !== null) { nextTasks.push(task); } @@ -5036,13 +5078,18 @@ export function makeClaudeAdapterV2( claudePendingBackgroundTask({ taskId: message.task_id, taskType: claudeTaskTypeFromSdkMessage(message), - startedByMonitor: yield* isClaudeMonitorTask(message.task_id, message.tool_use_id), + startedByMonitor: yield* isClaudeMonitorTask({ + nativeThreadId: input.nativeThreadId, + taskId: message.task_id, + toolUseId: message.tool_use_id, + }), description: typeof message.description === "string" ? message.description : undefined, }), ); rosterChanged = true; } else if (message.type === "system" && message.subtype === "task_notification") { + yield* endClaudeMonitorTasks((taskId) => taskId === message.task_id); const removed = yield* clearPendingBackgroundTask( input.nativeThreadId, message.task_id, @@ -5101,7 +5148,7 @@ export function makeClaudeAdapterV2( const message = input.message; // Before any routing: a Monitor started during an idle wake turn // reports its task before the drain replays the tool call. - yield* rememberClaudeMonitorToolUses(message); + yield* trackClaudeMonitorCalls(message); if (message.type === "rate_limit_event") { const rateLimitInfo = message.rate_limit_info; if (!rateLimitInfo) return;