Skip to content

[Bug]: Switching a Claude thread to Full access while an approval is pending leaves the tool call "running" forever ("Waiting on background task …") #25054

Closed

Description

@psicilia5179

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Searched for "Waiting on background task", stale/stuck background task, declined tool, and approval + full access. Closest is #15993: the same leftover running turn item, but triggered by t3_worktree_handoff. This report has a different and much more common trigger: answering an approval prompt by switching the thread to Full access and typing a reply.

Area

apps/server (orchestration V2, Claude adapter)

Steps to reproduce

  1. Claude thread (claudeAgent / claude-opus-5-5) in a mode that asks for approvals (Supervised / approval-required).
  2. Have the agent call a tool that needs approval, e.g. Bash touch repro-b.txt && date > repro-b.txt, or an MCP tool such as mcp__claude-in-chrome__navigate.
  3. While the approval prompt is showing, switch the thread's access mode to Full access, then type a reply ("Approved, you have full access") and send it, instead of pressing Approve.
  4. Let the new turn finish.

Expected behavior

The old tool call is settled (failed, cancelled or interrupted). Once the new turn ends, the composer shows nothing pending.

Actual behavior

  • The run that owned the approval ends failed with "The provider event stream closed unexpectedly. Retry the turn; if it keeps failing, check the provider and server logs."
  • The tool's turn item (dynamic_tool or command_execution) stays status: "running", completedAt: null, forever.
  • After every later turn, the composer shows "Waiting on background task Navigate" (or "PowerShell", or the command text). Tapping it does nothing. It goes away while a turn runs and comes back when the turn ends.
  • The approval item for that call also stays waiting in some cases (see below).

Reproduced on purpose in a fresh scratch thread, and found six more times in the state DB from normal use over three days. Every case shows the same sequence, to the millisecond:

Case Approval resolved Old run failed Next user message (web)
A (MCP navigate) 13:42:12.468 decline 13:42:12.495 13:42:12.535 "You have full approval. Continue"
B (MCP navigate) 19:24:36.257 decline 19:24:36.286 19:24:36.338 "You are approved with full access"
C (Bash) 19:39:10.350 decline 19:39:10.372 19:39:10.428
D (Bash) 17:15:57.893 decline 17:15:57.920 17:15:57.971
E (Bash) 16:38:13.191 decline 16:38:13.227 16:38:13.266
F (Bash) 20:03:11.940 decline 20:03:11.961 —
Scratch repro (Bash, sent from iOS) stays pending 22:03:56.141 22:03:56.428 "Approved now you have full access"

On web the composer declines the shown approval before sending (useComposerSend.ts, "A message sent while approvals are pending redirects the agent"). In the iOS repro nothing declined it, so the runtime request stayed pending and its item waiting, in addition to the stuck tool item.

The provider log shows what happens. Example, case A:

13:40:47.254  outgoing query.open   permissionMode: "default"
13:42:01.894  incoming assistant    tool_use toolu_… mcp__claude-in-chrome__navigate
              (approval shown; no tool_result ever arrives for this tool_use)
13:42:12.494  outgoing query.close
13:42:16.544  outgoing query.open   permissionMode: "bypassPermissions", resume: <same session>
13:42:16.544  outgoing prompt.offer "You have full approval. Continue"

The other cases and the scratch repro show the same pattern: query.open with "default" → query.close about 25 ms after the decline or send → query.open with "bypassPermissions".

Control

  • Steering while an approval is pending, without changing the access mode, works: same run, no query.close. The tool item ends failed with "Tool permission request aborted", and nothing is left running. (The approval item did stay waiting, with pendingRequestCount: 1 after the run completed. That may be a separate small bug.)
  • Approving normally works.

Analysis (from reading main @ 5f1f604)

  1. The send carries a runtime-mode change, so it can't steer the live query. A new run starts and ClaudeAdapterV2 replaces the query (existing.query.close in the query-open path, ~L9618). This closes the old query while the old turn still has an open tool_use and a pending canUseTool.
  2. The old turn's subscription sees the stream close, and the run is stored as failed with "provider event stream closed unexpectedly". Unlike the interrupt path (~L10149, which calls finalizeActiveTurnAfterQueryExit / endSubagentCallsOfClosedQuery), nothing here finalizes the turn's open tool items.
  3. RunExecutionService deliberately leaves command_execution / dynamic_tool items open after the run ends so late completions can land (backgroundCapableTurnItemTypes, ~L105 and ~L1246). The query that would send that completion is gone, so it never arrives.
  4. pendingBackgroundTurnItems / derivePendingBackgroundWork (packages/shared/src/orchestrationV2PendingBackgroundWork.ts) count any active item of those types once the latest run is settled, so the pill shows it forever.

Possible fixes: when the adapter replaces a query for a selection or permission change, end the old turn the way interrupt does, closing its open tool items and runtime requests. Or apply the mode change after resolving the pending approval in the same process. More generally, when a run ends failed because its query is gone, close its background-capable items too. This would also cover #15993.

Impact

Minor bug or occasional failure. It happens every time someone answers an approval this way, which is a natural thing to do. Nothing is running, but the thread looks busy indefinitely. The pill has no working action, so the only way to clear it is a server restart (not tried here; other threads were running).

Version or commit

0.1.2 (server and desktop). Same code paths on main @ 5f1f604.

Environment

Windows 11, desktop app with its local server. Seen in the desktop client and the iOS app. Provider claudeAgent, model claude-opus-5-5.

Logs or stack traces

Provider protocol log excerpt above. Server traces for the earlier cases had already rotated. Turn-item and runtime-request rows are from a read-only copy of statev2.sqlite.

Screenshots, recordings, or supporting files

Not attached (they contain private chat text). The pill reads "⚡ Waiting on background task Navigate" above an empty composer, with no Stop control.

Workaround

None found from the UI. Avoid switching the access mode while an approval is pending: press Approve or Decline first, then change the mode.


Investigated and filed by Claude Code (Claude Opus 5.5) on behalf of the reporter.

Activity

  1. psicilia5179 commented on Oct 25, 2026

    @psicilia5179
    Author

    One more symptom: the sidebar shows it too. The affected thread's row shows Waiting (dashed-circle status), with the provider's working badge, while nothing is running. The latest run is completed, there is no pending request, and the only thing "pending" is the stale tool item. So the thread looks busy from the thread list, not only in the composer pill. This probably also keeps it from settling or signalling that it is done (as described in #15993).

  2. psicilia5179 commented on Oct 25, 2026

    @psicilia5179
    Author

    More from the same scratch repro (iOS app, server 0.1.2). These are the runtime-request and turn-item rows afterwards, plus what the app showed.

    Approval cards for dead tool calls stay up and keep working buttons.

    Run What happened to the tool call Its approval card
    2 Steered while the approval was pending (no mode change). The Bash item ended failed with "Tool permission request aborted"; the run completed. Still shown as Approval needed with Allow once / Allow session / Decline, after the run completed and through runs 3–5.
    4 The query was replaced by the Full-access switch (this issue). The item is still running. Also still shown.
    5 Runs the same command again in Full access. No runtime request is created, so the command needs no approval. n/a

    While run 5 was running, the app showed "Running touch …" with an Approval needed card underneath, which looked like run 5's prompt. Tapping Allow once at 22:04:14 resolved run 2's dead request as accept. Run 5's command had already finished at 22:04:13 without asking. Tapping Decline at 22:04:37 resolved run 4's dead request as decline. The agent never saw either answer, since both queries were gone.

    Answering the stale card does not clear the stuck item. After run 4's request was resolved/decline (approval item cancelled), its command_execution item is still running with completedAt: null. The iOS composer shows it as a "Running: touch repro-b.txt && date > re…" pill, and the thread keeps showing as Waiting.

    Expected:

    • A runtime request whose tool call has ended (aborted by a steer, or its query closed) is cancelled. Its card goes away, or is shown as expired and can't be answered.
    • If a stale request is answered anyway, its tool item is closed too.

    The first point matches the "approval item stays waiting" note in the report: it happens even on the steer-only path, which otherwise works.

  3. psicilia5179 commented on Oct 25, 2026

    @psicilia5179
    Author

    Confirmed by the user who did the repro: in the scratch run they changed Runtime from Supervised to Full access in the iOS app's Runtime picker while the approval card was showing, then sent "Approved now you have full access". So the mode switch in the steps comes from the user's own action, not just from the permissionMode change in the provider log (default → bypassPermissions). In the six earlier cases it is inferred from that same log pattern.

  4. juliusmarminge commented on Oct 25, 2026

    @juliusmarminge
    Member

    Duplicate of #22957: changing a Claude thread’s runtime mode and sending while tool approval is pending closes the old query, fails its run, and leaves its command items running indefinitely. That report uses Supervised → Auto; this report adds Supervised → Full access and evidence of stale approval cards and sidebar status.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions