# fix\(server\): Claude background subagents get approvals after the root turn ends · PR \#20347 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/20347)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: open

Source repository: Unavailable fork

Source branch: fix/20339-claude-background-subagent-permissions

Target branch: main

Head commit: 73b573182edea3ec0916e3e3c39f91e5548b4ccc

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/20347)

[Commits](https://git.cafe/pingdotgg/t3code/pull/20347/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/20347/diffs?format=markdown)

## Description

---

## Problem

When a Claude thread's root turn ended while a background subagent was still running in the same CLI process, every permission callback from that subagent was denied with `Claude V2 adapter has no active turn for this tool request.` No approval was raised, and the thread's runtime policy was never consulted. The root `result` cleared `activeTurn`, and `canUseToolEffect` treated `null` as "deny". Repro: in a Supervised thread, have Claude launch an `Agent` with `run_in_background: true` that reads `/etc/hosts`, and end the root turn. Once the root completes, the subagent's `Read` and `cat` fail and the thread records no runtime request. Resuming a completed foreground subagent with `SendMessage` hits the same path.

## Change

- When `activeTurn` is `null`, `canUseToolEffect` falls back to the settled (completed) turn of the run that launched the calling subagent. A running subagent keeps its launching run's `runId`, and that run keeps ingesting while the subagent is alive, so settled contexts are kept per completed run until none of their subagents are running. A later wake continuation, or a failed later turn, no longer strands an earlier run's subagent. Every one of these must hold:
  - the callback belongs to the current live query (each query's `canUseTool` now carries its own `closed` deferred);
  - that query is not stopping;
  - the signal is not aborted;
  - a subagent of that run is still running and matches the callback's `agentID`, or any one when the SDK sends none;
  - that subagent does not come from an earlier process.
- Everything else is still denied: subagents of interrupted or failed runs, stopped queries, stale processes and aborted callbacks.
- The request then goes through the same approval flow as a live turn. The approval node sits under the run's root node, and no tool item is invented for the settled turn.
- If Claude aborts the callback before it is answered (subagent stopped, thread stopped, process replaced), the adapter emits the request, its node and its turn item as `cancelled`. The orchestrator's run-interrupt cleanup does not cover an already-completed run, so without this the approval stayed `pending` with nothing left to answer it.

Builds on the original fix by @ScottN-PV in this PR. Tests from the earlier takeover are kept; this update adapts them to main's full-access approvals ([#22546](https://github.com/pingdotgg/t3code/pull/22546)) and adds the abort, wake-continuation and failed-later-turn cases.

## Scope and approval

Fixes #20339
GitCafe: https://git.cafe/pingdotgg/t3code/issues/20339
Triaged bug via the automated issue flow; reproduced on main by the triage run in the real web client (see Verification).

## Verification

**Focused tests** in `apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts`, at [73b573182e](https://github.com/pingdotgg/t3code/commit/73b573182e):
- Full adapter file: 214/214 pass.
- Negative control: with main's `ClaudeAdapterV2.ts`, the 4 settled lifecycle tests fail (denied instead of asked).
- Each later case failed before its change:
  - abort: `expected 'pending' to equal 'cancelled'`;
  - second background subagent after a wake continuation settles: `Permission returned before approval: deny`;
  - earlier run's subagent after a later turn fails with `api_error`: `Permission returned before approval: deny`.
- `tsc --noEmit` for `apps/server`: 0 errors. Lint on both files: no new warnings (the existing unused `layer` warning is on main).

**Runtime check.** Linux web dev client (headless Chromium, 1440×1000, light). Claude CLI 2.1.303, Claude Sonnet 5.5, Supervised mode, the same prompt on both builds. The prompt asks Claude to launch one background `Agent` that sleeps, then reads `/etc/hosts` with `Read` and `cat`, and to end the root turn at once.

| | Before: main [f054d1b801](https://github.com/pingdotgg/t3code/commit/f054d1b801) | After: [d5a51b5747](https://github.com/pingdotgg/t3code/commit/d5a51b5747) (same adapter as head) |
|---|---|---|
| Root run completed | 14:18:49Z | 14:14:03Z |
| Subagent `Read` / `cat` | `no active turn` error at 14:18:58Z and on each retry; 0 runtime requests | `file-read` approval at 14:14:15Z, approved; `cat` approval at 14:15:02Z, approved; subagent read the file |
| Capture | ![Before: subagent tool error, no approval](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c44fa357172362a7/before-no-active-turn-error.png) | ![After: pending file-read approval after the root run completed](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/31089d28f7a0634e/after-pending-approval.png) |

![After: pending cat approval](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5bf1c5898c5ebeb7/after-pending-cat-approval.png)

Transcript with timestamps and log excerpts: [transcript.md](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d489a4df1eadcac5/transcript.md). The runtime run was single-subagent, before the abort, per-run and failed-turn commits. Those cover paths a single subagent does not reach and are proven by their tests.

**Not checked live:** the abort cancellation and multi-run cases (covered by tests only), and full-access threads under `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB`. Also not checked: the macOS desktop app, mobile rendering of an approval under a completed run, and remote/relay/tunnel connections. Server-only change, no client code touched.

Prepared with Claude Code (claude-opus-5-5 implementation and review, claude-sonnet-5-5 review) in T3 Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

