[Bug]: Stopped Claude thread silently loses native context when switching compatible provider instances
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Update 2026-09-02: reproduced on v0.0.38; see the latest comment and proposed fix #6148.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Configure two enabled Claude provider instances, A and B, that use the
claudeAgentdriver and resolve to the same Claude home / continuation key. - Start a T3 thread on instance A and complete enough turns to persist a
non-empty Claude resume cursor. - Leave the thread idle until its provider session is soft-stopped.
- In the existing thread, select instance B before sending the next message.
- Send a short reply that depends on the immediately preceding assistant
message, such asAin response to a list of proposed options.
The failure boundary is important: switching compatible instances while the
old provider session is active has a path that carries
activeSession.resumeCursor; switching after the session has stopped does
not.
Expected behavior
Because A and B advertise the same continuation identity, T3 should explicitly
carry the prior Claude resume cursor into B. If that is not safe for a given
pair of instances, T3 should reject the switch or require a transcript
handoff before sending the message.
A non-empty T3 thread should never silently start a blank provider-native
session.
Actual behavior
T3 keeps the full message history visible in the same UI thread but starts a
new Claude Agent SDK session without a resume cursor. Claude receives only the
new short message and replies as if the earlier conversation never happened.
In the observed incident:
- The old Claude JSONL remained present and readable.
- Neither the old nor new JSONL contained a
compact_boundaryentry. - The execution host, cwd, model, Claude home, driver, and continuation key
were unchanged. - The replacement native session's persisted cursor became
turnCount: 1. - T3's projected messages and checkpoints remained intact.
This rules out context-window exhaustion, compaction, missing native session
data, or a UI-history deletion.
The stopped-session path appears to lose the previous instance identity before
the turn starts:
- The model-selection update to B is projected before the turn-start event.
- With no active session,
ProviderCommandReactor.ensureSessionForThread
derivescurrentInstanceIdfrom the already-updated
thread.modelSelection.instanceId. - The reactor therefore sees B as both the current and desired instance and
starts it without explicitly carrying A's cursor. ProviderService.startSessiononly falls back to the persisted cursor when
persistedBinding.providerInstanceId === resolvedInstanceId.- The persisted binding belongs to A while the target is B, so the cursor is
rejected even though both instances have the same continuation key. ClaudeAdapterconsequently supplies a newly generatedsessionIdrather
thanresumeto the Agent SDK.
Related but distinct reports:
- #2188 covered idle/restart recovery on the same provider session. This repro
is the remaining stopped-session + compatible-instance-switch boundary. - #2365 discusses why genuinely different Claude-compatible endpoints may not
be safe to resume across. - #3797 proposes transcript handoff for incompatible provider/model switches.
Impact
Major degradation or frequent failure
The reset is silent and the UI still displays the old conversation, so the
user cannot tell that approvals, architectural decisions, constraints, and
prior tool results are absent from the model's context.
Version or commit
Observed on v0.0.29 (1153afb4). The relevant logic is unchanged on main at
887dd6e455bb / v0.0.30-nightly.20260728.933.
Environment
T3 Code Desktop on macOS with a remote Linux execution host; Claude Code
2.1.218 via two configured claudeAgent instances; same model, host, cwd,
Claude home, and continuation key across the switch.
Logs or stack traces
# Last context-bearing turn completes on instance A.
# The provider session is later soft-stopped while idle.
# The user selects compatible instance B immediately before the next turn.
provider.instance_id=B
provider.resume_cursor.present=false
provider.resume_cursor.source=none
claude.resume.source=generated-session
claude.query.resume=""
# After the context-free response:
resumeCursor.turnCount=1
No credentials, prompt contents, project paths, or full local identifiers are
included above.
Suggested fix and regression test
Resolve the previous provider instance and resume cursor from the persisted
binding or stopped session before the newly projected model selection replaces
that identity. Then:
- If continuation identities match, explicitly pass the prior cursor to the
target instance. - If they do not match, reject the switch or use a reviewed transcript handoff.
- Emit a warning/activity entry when a thread with completed turns starts a
provider session without a resume cursor.
Regression test:
- Start a Claude thread on instance A.
- Persist a non-empty cursor and soft-stop the session.
- Update the selection to compatible Claude instance B.
- Start the next turn.
- Assert that B receives A's prior cursor, or that the switch is explicitly
rejected before the user message is sent. It must not silently start blank.
Screenshots, recordings, or supporting files
No response.
Workaround
Use one provider instance per T3 thread. When changing route or provider
instance, start a new T3 thread with a reviewed handoff. If the original
Claude session still exists, resume it directly from the same Claude home and
working directory.
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 in apps/server by tracing ProviderCommandReactor.ensureSessionForThread, ProviderService.startSession, and ClaudeAdapter through the stopped-session and instance-switch path. Reproduce the regression with compatible instances A and B, then verify that B receives A's persisted cursor or that the switch is rejected before sending the message, with no silent blank session.
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
- 55/100