Repository navigation
[Bug]: Switching a Claude thread to Full access while an approval is pending leaves the tool call "running" forever ("Waiting on background task …") #25054
Description
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
- Claude thread (
claudeAgent/claude-opus-5-5) in a mode that asks for approvals (Supervised / approval-required). - 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 asmcp__claude-in-chrome__navigate. - 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.
- 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
failedwith "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_toolorcommand_execution) staysstatus: "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
waitingin 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 endsfailedwith "Tool permission request aborted", and nothing is left running. (The approval item did staywaiting, withpendingRequestCount: 1after the run completed. That may be a separate small bug.) - Approving normally works.
Analysis (from reading main @ 5f1f604)
- The send carries a runtime-mode change, so it can't steer the live query. A new run starts and
ClaudeAdapterV2replaces the query (existing.query.closein the query-open path, ~L9618). This closes the old query while the old turn still has an opentool_useand a pendingcanUseTool. - The old turn's subscription sees the stream close, and the run is stored as
failedwith "provider event stream closed unexpectedly". Unlike the interrupt path (~L10149, which callsfinalizeActiveTurnAfterQueryExit/endSubagentCallsOfClosedQuery), nothing here finalizes the turn's open tool items. RunExecutionServicedeliberately leavescommand_execution/dynamic_toolitems 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.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
psicilia5179 commentedon Oct 25, 2026
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).psicilia5179 commentedon Oct 25, 2026
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.
failedwith "Tool permission request aborted"; the run completed.running.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 asdecline. 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 itemcancelled), itscommand_executionitem is stillrunningwithcompletedAt: null. The iOS composer shows it as a "Running: touch repro-b.txt && date > re…" pill, and the thread keeps showing as Waiting.Expected:
The first point matches the "approval item stays
waiting" note in the report: it happens even on the steer-only path, which otherwise works.psicilia5179 commentedon Oct 25, 2026
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
permissionModechange in the provider log (default→bypassPermissions). In the six earlier cases it is inferred from that same log pattern.juliusmarminge commentedon Oct 25, 2026
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.