Skip to content

[Bug]: A held queued continuation run jams the Claude adapter's continuation gate: later wakes are swallowed, wake-turn output is never recorded, and background subagents stay Running #20430

Open

Description

@aabdel0181

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

Not reproduced deterministically; this is a reconstruction from one thread's statev2.sqlite, provider event log and boot-service.log on 0.0.46-nightly.20261015.3145 (server up since 2026-10-15 08:02 UTC, never restarted). The sequence that produced it:

  1. A Claude thread runs background subagents (Agent, run_in_background: true) and ends its turn. Their completions produce wake continuations, which run normally for hours (claude-wake-turn-detected is logged 12 times for the thread).
  2. At 19:57:56 UTC a wake continuation (run 31) completes. At 19:57:57 the adapter logs claude-wake-turn-detected twice; the second offer is dispatched as run 32 (message "Background task completed.") and lands in queued, since run 31 was still finalizing.
  3. Run 32 never starts: its projection row carries queueHeld: true, so canStartQueuedRun excludes it on every later run finalization, and needsRecovery("runtime") excludes held queued runs too. User prompts (runs 33 to 50) start and complete normally around it. The UI shows nothing about a held queue on this thread.
  4. From this point on, claude-wake-turn-detected is never logged for the thread again (other threads keep getting wakes all day).

Expected behavior

A continuation run that cannot start should be dropped and the adapter's continuation request released, so the next wake can be offered again. A queue hold on a server-generated continuation message should not need a user to click resume. Background subagents whose completion arrives while the thread is idle should end in the projection.

Actual behavior

The Claude adapter's requestedContinuations entry for the native thread (bufferWakeMessage, the "sticky offer") is only cleared when a continuation turn drains the wake buffer, or on process loss. With run 32 held, the entry stayed set for the rest of the process's life. Everything after that follows from the one stuck bit:

  • Every wake turn the CLI ran while the thread was idle was invisible to T3. Between run 38's end (21:12:30 UTC) and run 41 (22:50:39 UTC) the CLI ran wake turns with assistant output, a SendMessage that resumed one subagent (21:17:56) and a new Agent launch (21:19:10). T3 recorded 26 events in that window, all subagent progress and roster updates: no run, no items.
  • A background subagent (task:ac1d42bf0362bfc4c, run 38) completed at 21:18:21 UTC; the SDK delivered task_updated and task_notification {status: completed} 10 ms apart, followed by the wake init. The notification was buffered for a continuation that never came, so the row stayed running (last update 21:12:06). A sibling subagent that completed 65 s earlier (21:17:16) was applied at once through the settled-turn path, so the idle path itself works; what broke is what happens once the wake buffer holds a task_started or an earlier frame for the thread.
  • The idle release deferred twice (driver-session.idle-release-deferred at 22:01 and 22:31 UTC) because the adapter reported pending background work for subagents it could no longer finish.
  • When the CLI process finally exited (parent idle), finalizeActiveTurnAfterQueryExit ended only workflow subagents ([Bug]: A background Claude subagent stays running forever when the Claude process exits while its parent is idle #19440) and clearWakeStateForNativeThread dropped the buffer, buffered completion included. The row stayed running for a day; startup recovery would cancel it (needsRecovery("runtime") includes threads with an open native subagent), but the server never restarted.

Two defects, as far as I can tell from main at 339179f:

  1. ProviderContinuationService dispatches the adapter-buffered continuation with queue_after_active, and Orchestrator.startNextQueuedRun can hold that queued run (queueHeld: true) like a user message. A held continuation is dead weight: there is no user to resume it, and nothing cancels it.
  2. Claude's continuationRequests.offer sets neither clearIfCurrent nor dispatchIfCurrent, so the orchestrator has no way to release the adapter's requestedContinuations entry when the continuation it guards is held, cancelled or dropped. fix(server): Claude background subagents end when their wake can't run on Claude #17109 adds clearIfCurrent for the provider-switch drop in [Bug]: Claude background subagents stay Running forever when their wake continuation runs on another provider #17099; the same hook would need to fire when a queued continuation run is held or cancelled. Once the entry is released, the next wake evidence re-offers, and a continuation that finds an empty buffer already settles immediately.

Related: #19440 and #19464 (ending subagents when the process exits, which would have bounded the damage here), #18910 (a completion that arrived but was not applied; the mechanism above may be one way that happens), #17099 and #17109 (the same sticky entry, different trigger).

Impact

Major degradation or frequent failure

Version or commit

0.0.46-nightly.20261015.3145; main at 339179f has the same paths

Environment

Linux (Ubuntu, systemd user manager), web client, Claude Code CLI on the claudeAgent driver (instance claude-public-pool, model claude-fable-5-1); background subagents on claude-sonnet-5-5

Logs or stack traces

# orchestration_v2_projection_runs, the thread's runs 31-33 (UTC)
31 completed  19:57:56.927 -> 19:57:56.956   (wake continuation)
32 queued     19:57:57.026 -> (never)        text "Background task completed."  queueHeld=true
33 completed  20:07:37     -> 20:09:30       (user prompt; later user runs 34-50 also start normally)

# boot-service.log, claude-wake-turn-detected for this thread: 12 entries up to 19:57:57.018, none afterwards
[12:57:56.904] INFO orchestration-v2.claude-wake-turn-detected  threadId: '618a5f71-...'
[12:57:57.018] INFO orchestration-v2.claude-wake-turn-detected  threadId: '618a5f71-...'

# provider event log, the lost completion (UTC)
21:17:16.343Z system/task_updated      task_id=a200992c5c0dd05ec patch={status:"completed"}   -> applied at 21:17:16.346Z
21:17:16.343Z system/task_notification task_id=a200992c5c0dd05ec status=completed
21:17:16.475Z system/init                                                                      (wake turn, swallowed)
21:17:56.672Z system/task_started      task_id=ab0d0ecdbe27a93d7 tool_use_id=toolu_01YWyvSD   (SendMessage resume, root idle, no run)
21:18:21.737Z system/task_updated      task_id=ac1d42bf0362bfc4c patch={status:"completed"}
21:18:21.747Z system/task_notification task_id=ac1d42bf0362bfc4c status=completed             -> never applied
21:18:21.748Z system/background_tasks_changed tasks=[ab0d0ecd]
21:18:22.007Z system/init                                                                      (wake turn, swallowed)

# orchestration_v2_projection_subagents, a day later
node:provider:claudeAgent:native-item:task%3Aac1d42bf0362bfc4c  status=running  started_at=2026-10-15T21:02:40.846Z  updated_at=2026-10-15T21:12:06.049Z  completed_at=NULL

Screenshots, recordings, or supporting files

No response

Workaround

Cancel the held continuation from the thread's queue (t3_queue_cancel over MCP, or the queue UI). The adapter's entry is per process, so a later CLI process starts clean. The stale subagent row clears at the next server restart.

Activity

  1. juliusmarminge commented on Oct 17, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on main (0912b24). This is a real gap that the two open PRs don't fully close, so it stays open.

    • The hold: ProviderContinuationService dispatches the Claude wake as a queue_after_active message. A queued run inherits queueHeld whenever another held queued run exists (Orchestrator.ts, message.dispatch), and startNextQueuedRun / canStartQueuedRun skip a thread with any held run. fix(server): Claude background-task wakes start when the queue is held #17885 stops provider wakes from inheriting a hold, but by its own description it leaves the latch alone.
    • The latch: Claude's continuationRequests.offer passes no clearIfCurrent or dispatchIfCurrent. requestedContinuations is cleared only when a continuation turn attaches, or by clearWakeStateForNativeThread. fix(server): Claude background subagents end when their wake can't run on Claude #17109 adds those hooks, but only calls them on an archive, a provider switch or a failed dispatch. Nothing calls them when a queued wake is held or cancelled. queued-run.cancel has no continuation hook either, and Stop cancels queued provider wakes, so it leaks the latch the same way.

    One correction to the workaround: cancelling the held run in the queue does not release the latch. Wakes come back only when a new Claude CLI process starts.

    One question: before run 32, did this thread already have an older held queued run, for example one held by startup recovery at 08:02 UTC? That tells us whether #17885 alone would have prevented the hold in your case.

    Related: #17099 (same latch, provider-switch trigger), #19440 / #19464 (ending subagents when the process exits), #18910 (possibly the same mechanism).

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions