pingdotgg / pingdotgg/t3code

[Bug]: Claude session replacement suppresses exit and silently stops live subagent tasks

Open
#12,694 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
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, and thread.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

  1. Start a Claude provider session and leave one or more background/subagent
    tasks live.
  2. Submit thread.turn.start in a condition where
    ProviderCommandReactor.ensureSessionForThread chooses its restart branch.
    At commit c7fa1e96, this happens when runtime mode, cwd, provider instance,
    an unsupported model switch, or Claude model-selection equality requires a
    restart.
  3. The reactor calls providerService.startSession while the adapter still has
    an existing context for that thread.
  4. 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:

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.