[Bug]: Claude session replacement suppresses exit and silently stops live subagent tasks
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched open and closed issues for the replacement warning, suppressed
exit behavior, stopped live tasks, andthread.turn.start; I found no
matching report. - This report includes the observed event chain and source locations needed
to investigate it.
Area
apps/server
Steps to reproduce
- Start a Claude provider session and leave one or more background/subagent
tasks live. - Submit
thread.turn.startin a condition where
ProviderCommandReactor.ensureSessionForThreadchooses its restart branch.
At commitc7fa1e96, this happens when runtime mode, cwd, provider instance,
an unsupported model switch, or Claude model-selection equality requires a
restart. - The reactor calls
providerService.startSessionwhile the adapter still has
an existing context for that thread. - Inspect orchestration events and provider session state.
The narrower predicate that triggered each observed replacement had already
rotated from the local trace, so this report does not claim which predicate was
true. The replacement/stop chain itself was observed twice: once while the old
session was running with four live tasks, and once while it was ready but
still owned one live task.
Expected behavior
An incoming turn should reuse the active provider session when compatible. If a
replacement is required, T3 should emit a durable, reasoned session-boundary
event (or reject/defer the replacement) so the orchestration timeline and the
seat can detect that the old session ended and that live child tasks were
stopped.
Actual behavior
ClaudeAdapter.startSession detects the existing context, logs
claude.session.replacing, and calls
stopSessionInternal(existingContext, { emitExitEvent: false }).
stopSessionInternal closes the query, records every liveTaskId as
task.completed(status=stopped), and suppresses session.exited. A replacement
session then starts, but there is no thread.settled,
thread.session-stop-requested, stopped session row, provider error, or other
durable boundary explaining why the live tasks disappeared. In the observed
cases, the seat learned about the replacement only because its provider harness
name changed.
Impact
Major degradation or frequent failure. An ordinary incoming turn can silently
kill in-flight subagents while the parent thread continues, and the durable
session projection does not expose the boundary needed for recovery.
Version or commit
0.0.43-nightly.20260918.1895, inspected source commit
c7fa1e96943f380a8893e89460d03a362285ab83.
Environment
Self-hosted Linux container; Claude provider (CLI 2.1.277). The container stayed
healthy and did not restart during either event.
Sanitized evidence
thread.turn-start-requested
claude.session.replacing existingSessionStatus=<running|ready>
task.completed status=stopped # four tasks in one event, one in the other
replacement session starting/running
Absent from both chains:
thread.settled
thread.session-stop-requested
session.exited / durable stopped session row
provider error
container restart
Relevant source at the inspected commit:
ProviderCommandReactorrestart predicates and decision logClaudeAdapter.startSessionreplacement withemitExitEvent: falsestopSessionInternalstops live tasks and conditionally suppressessession.exited
Workaround
No safe server-side workaround is known. Operationally, background work must be
made resumable and reconciled after an incoming turn. Avoiding replacement-capable
turn delivery prevents the symptom but is not a product fix.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ProviderCommandReactor.ts at the restart predicates and decision log, then trace ClaudeAdapter.ts through startSession and stopSessionInternal. Reproduce a replacement with thread.turn.start while live tasks exist and compare the orchestration events and session state. Done means a required replacement exposes a durable session boundary and does not silently discard live-task termination.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100