Repository navigation
[Bug]: Claude wake run stays running forever when a queued user message answers its native turn first #22510
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
Deterministic with the adapter's wake test harness. The test below fails on main at ca140c435f:
- A Claude user turn ends with its
result. No T3 turn is active. - Claude opens a native turn on its own (a wake) and emits a root
system/init.bufferWakeMessagebuffers theinitand offers a continuation, so T3 queues a continuation run. - A user message that was queued earlier (here, a PR watch notification) starts its run before the continuation does and sends its prompt.
- Claude folds that prompt into the native turn it already opened. One native turn and one
resultserve both. The user run takes theresultand completes. - The continuation run starts. It replays the stale buffered
init, marks the wake as started, and waits for aresultthat already went to the user run.
Failing test (drop into the "ClaudeAdapterV2 background wake turns" describe in ClaudeAdapterV2.test.ts)
it.effect("settles a wake run whose native turn a user turn already answered", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeWakeHarness;
const now = yield* DateTime.now;
const startTurn = (ordinal: number, text: string, wake = false) =>
harness.runtime.startTurn(
makeClaudeTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now,
attemptId: RunAttemptId.make(`attempt-claude-wake-answered-${ordinal}`),
text,
attachments: [],
providerTurnOrdinal: ordinal,
...(wake
? { messageCreatedBy: "agent" as const, messageCreationSource: "provider" as const }
: {}),
}),
);
const lifecycle = (promptIndex: number, state: string, uuid: string) =>
claudeSdkFrame({
type: "command_lifecycle",
command_uuid: harness.promptUuid(promptIndex),
state,
uuid,
session_id: WAKE_NATIVE_SESSION,
});
yield* startTurn(1, "Open the PR.");
yield* harness.offerAndWait(
claudeSdkFrame({
...makeResultFrame({ uuid: "00000000-0000-4000-8000-000000000a01", result: "Opened." }),
user_message_uuid: harness.promptUuid(0),
}),
);
assert.equal((yield* Queue.take(harness.terminalReceipts)).status, "completed");
// Claude opens a native wake turn on its own while T3 is idle.
yield* harness.offerAndWait(wakeTurnInit);
yield* Queue.take(harness.continuationReceipts);
// A queued user message runs before the continuation. Claude folds it
// into the wake turn it already opened and ends both with one result.
yield* startTurn(2, "A check failed on the PR.");
for (const frame of [
lifecycle(1, "queued", "00000000-0000-4000-8000-000000000a02"),
lifecycle(1, "started", "00000000-0000-4000-8000-000000000a03"),
makeAssistantTextFrame({
uuid: "00000000-0000-4000-8000-000000000a04",
text: "Fixed the check.",
}),
lifecycle(1, "completed", "00000000-0000-4000-8000-000000000a05"),
claudeSdkFrame({
...makeResultFrame({
uuid: "00000000-0000-4000-8000-000000000a06",
result: "Fixed the check.",
}),
user_message_uuid: harness.promptUuid(1),
}),
]) {
yield* harness.offerAndWait(frame);
}
assert.equal((yield* Queue.take(harness.terminalReceipts)).status, "completed");
// The continuation has nothing left to answer, and Claude is idle.
yield* startTurn(3, "Background task completed.", true);
yield* TestClock.adjust("2 minutes");
assert.lengthOf(harness.terminalEvents(), 3);
assert.equal((yield* Queue.take(harness.terminalReceipts)).status, "completed");
assert.lengthOf(harness.offeredMessages, 2);
}).pipe(
Effect.provide(Layer.mergeAll(IdAllocator.layer, McpProviderSessions.layer, NodeServices.layer)),
),
),
);It fails with expected [ …(2) ] to have a length of 3 but got 2: the continuation never reaches a terminal state, even after the 2-minute deadline.
Expected behavior
The continuation run settles as completed. The user run already answered the native turn it was opened for, and Claude is idle.
Actual behavior
The continuation run stays running forever, and every run queued behind it waits. In my case that was 5 hours: a review result, two delegated-task completions and two PR watch updates sat queued while the thread showed Working and the Claude CLI sat idle. It only moved when another thread sent a message, which T3 delivered into the stuck run.
The cause is in ClaudeAdapterV2.ts. The user-turn path in startTurn leaves the buffered root init and the requestedContinuations entry in place. When the continuation drains the buffer, replaying that init through handleSdkMessage sets context.nativeWakeStarted = true. No buffered result follows, so nothing finalizes. The #19196 deadline only fires while nativeWakeStarted is false, so it never fires here.
Pi, Muse and OpenCode hit the same race and handle it. When a user turn starts while a wake is held, the user turn takes the wake over and the later continuation finishes at once (OpenCode takeRunningWake, Pi's adoptedWake, Muse's if (continuation && !held) return finish(...)). I checked the other adapters: Codex, Grok, ACP-registry and Antigravity can't hit it, and Cursor doesn't request continuations.
Related, but different: #22033 has the same root weakness (a user turn leaves an old buffered start in the wake buffer), but there the symptom is a subagent stuck Running. #20430 is a continuation run that never starts because it's held in the queue. Here the run starts and never ends. #22049 and #20318 don't fix this; the test above still fails with each applied.
Impact
Blocks work completely
Version or commit
0.1.1-nightly.20261020.3411; still fails on main @ ca140c4
Environment
Linux (WSL2), T3 server as a background service, Claude Code CLI 2.1.304, claude-opus-5-5, full access
Logs or stack traces
# provider event log, one Claude CLI process (UTC)
13:56:02.563 incoming result success # run 9 ends
13:56:02.616 incoming command_lifecycle started 0b35bb54 # no T3 prompt: Claude starts a wake
13:56:02.636 incoming system/init # adapter buffers it, logs claude-wake-turn-detected, run 11 queued
13:56:03.671 outgoing prompt.offer # run 10 (queued earlier) sends its prompt
13:56:03.673 incoming command_lifecycle queued 511fd19c
13:56:07.090 incoming command_lifecycle started 511fd19c
14:00:36.918 incoming command_lifecycle completed 511fd19c
14:00:36.921 incoming result success # one result for both; run 10 completes
14:00:36.929 incoming command_lifecycle completed 0b35bb54
(no events for 5 hours; run 11 started 14:00:37.707 and stays running)Workaround
Send any message to the stuck thread. It's delivered into the stuck run, Claude starts a turn, and its result finally ends the run. I didn't try Stop.
Activity
juliusmarminge commentedon Oct 20, 2026
Note
Grok responding on behalf of Julius.
Thanks for the failing test, it maps cleanly onto current main. When the continuation run starts, it replays every buffered frame except
resultthroughhandleSdkMessage(apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts:9520-9545). A replayed rootinitsetsnativeWakeStarted(lines 8559-8566). The two-minute fallback that settles a wake Claude never opens only runs whilenativeWakeStartedis false (lines 9546-9575). So when a queued user run has already taken theresultfor the turn Claude opened, the staleinitmakes the continuation look started, and nothing is left to end it.This isn't covered by the existing reports. #20430 needs a held queued run blocking the continuation gate, #17099 is a wake continuing on another provider, and #17809 is about subagent rows after a completion wake. #21239 drops obsolete pull request watch notifications before they start, which would avoid this particular trigger only when the watch is stale, not the race itself.
Accepting this as a bug.
fix(server): a Claude wake run settles when a queued message already …
fix(server): a Claude wake run settles when a queued message already …
kkolodziej7 commentedon Oct 23, 2026
Confirming this on the released desktop builds 0.1.1 and 0.1.2 (macOS,
claudeAgent, Claude Code CLI 2.1.308 and 2.1.309,claude-opus-5-5). It hit one long-lived thread three times in about 13 hours on Oct 22–23. Each time the sequence matched this report:createdBy=agent,creationSource=provider).queue_after_active) starts first. The native turn ends with one successfulresult, and the user run takes it and completes. In the case we inspected, thatresulthad task-notification origin,num_turns=7andqueued_turn_count=0.running, and everything queued behind it waits.resulttaken by the user run (UTC)During the waits nothing else was pending: no runtime request, open tool, hook or compaction, and the CLI had no open TCP connection. The two-minute wake deadline did not fire in any of the three cases, which fits the replayed
initsettingnativeWakeStarted. To recover, the stuck run had to be interrupted and the 7 messages queued behind it cancelled by hand, then the thread restarted.0.1.2 still has this. Could #22513 make the next release?
fix(server): a Claude wake run settles when a queued message already …