Worktree handoff during a streaming turn: ClaudeAdapterV2.interruptTurn fails (ProviderAdapterInterruptError) and ProjectionStore.getThread reports thread not found#25146
What happened
An agent running inside a thread called t3_worktree_handoff with a continuationPrompt to move its own thread (2e840833-8286-4529-9b7b-9d89bc0f833b) into a new git worktree (wave-w163-hypermedica-devops). Turn ordinal 11 was still streaming when the handoff started.
From the user's side the handoff succeeded: the continuation started in the new worktree and work continued, and the server stayed up. The only sign of trouble was two failures in the server trace, both under the same trace id face34502590e50a63555c6d48352f89:
ClaudeAdapterV2.interruptTurn→ProviderAdapterInterruptError:Failed to interrupt claudeAgent provider turn
provider-turn:provider:claudeAgent:native-turn:turn%3A...thread%253A2e840833-8286-4529-9b7b-9d89bc0f833b%253Aordinal%253A11:attempt:1- Shortly after, in the same trace,
ProjectionStore.getThread→ProjectionStoreThreadNotFoundError:No orchestration projection exists for thread ...
Both lines were read directly from server.trace.ndjson on the affected server (not inferred).
Expected: the handoff interrupts the running turn cleanly, and the projection lookup finds the thread.
Repro steps
- Run T3 Code server 0.1.2 (
t3 serve, systemd user service) with aclaudeAgentthread. - While a turn is actively streaming, have the agent in that thread call
t3_worktree_handoffwith acontinuationPromptpointing at a new git worktree. - Inspect
server.trace.ndjson: a single trace containsProviderAdapterInterruptErrorfromClaudeAdapterV2.interruptTurn, followed byProjectionStoreThreadNotFoundErrorfromProjectionStore.getThread.
Observed once, on 2026-10-25 ~23:12 UTC. The trace files contain exactly one ProviderAdapterInterruptError, so reliability of repro is unknown.
Diagnosis (hypotheses — not yet mapped to source)
- Interrupt runs after teardown / after the turn ended. The handoff may replace or close the provider session before
interruptTurnruns, or the turn may already have finished. The adapter then finds no live turn and raisesProviderAdapterInterruptErrorinstead of treating "turn already gone" as a no-op. - Projection lookup races the move. The
getThreadin the same trace may run before the projector has written the handed-off thread (or after the old row was replaced). - Possible double percent-encoding of the turn id (observation). The id contains
%253A(%3Aencoded twice). If the id is single-encoded on store but double-encoded on lookup (or vice versa), the interrupt could miss a turn that is still live.
The continuation still ran, so this looks like a cleanup/ordering bug in the handoff path rather than data loss. Worst case: a provider turn left running on the old session after the handoff.
Environment
- Server version: T3 Code 0.1.2 (
t3 serveas systemd user servicet3code.service) - Triage CLI: 0.1.2
- Surface: local server on a Linux VM (not website/mobile)
- OS: Linux x64
- Provider:
claudeAgent(ClaudeAdapterV2) - Handoff caller: the agent inside the thread
Evidence
- Trace id:
face34502590e50a63555c6d48352f89 - Thread
2e840833-8286-4529-9b7b-9d89bc0f833b, turn ordinal 11, attempt 1 - Target worktree:
wave-w163-hypermedica-devops - The two error lines quoted above, read from
server.trace.ndjson
For maintainers
Useful to pull: the full span tree for this trace id, logs/provider/events.log around 23:12 UTC, and the thread's projection rows before/after the handoff.
Filed via t3 triage (investigation and evidence confirmed from the server trace by Claude).