Skip to content

Claude thread loses context and cannot fork after its provider association moves to an imported thread #17236

Description

@Lermatroid

What happened

An existing Claude conversation continued displaying its history in T3, but the agent could no longer see the earlier messages. Forking from a response before the context loss also failed.

Investigation found a separate imported thread associated with the original Claude session. Attempting to continue that imported thread failed with a database uniqueness error.

The conversation text and native Claude session file were still present. Recovery required exporting the saved messages and having a fresh thread read them.

Diagnosis

The persisted state suggests a collision between an existing thread and an imported representation of the same native Claude session.

Using synthetic identifiers:

  • Original application thread A has runs 2–19 referencing provider thread P.
  • The provider-thread record for P has both thread_id and payload_json.appThreadId pointing to import:claudeAgent:SESSION_X, rather than A.
  • The imported thread’s active provider thread is P.
  • Run 20 in A starts a different native Claude conversation. Runs 20–22 have no recorded contextHandoffId.
  • A fork from run 19 fails because T3 cannot resolve the source provider thread.
  • Continuing the imported thread fails on the unique constraint for provider-thread ID plus turn ordinal.

The fork error matches the guard in Orchestrator.ts that rejects a pending fork when its source run or source provider thread cannot be resolved.

Suspected cause: importing the native session reassigned a provider-thread record already referenced by the original application thread. The resulting database state is confirmed; the exact action or background process that produced it has not been established. Source inspection used main, not an exact build checkout.

Steps to reproduce

A deterministic reproduction has not yet been established. The observed sequence was:

  1. Use an existing Claude thread over multiple turns.
  2. Continue the thread later. Its earlier messages remain visible, but Claude lacks their context.
  3. Fork from a response before the context loss.
  4. Send a message in the fork. Dispatch fails.
  5. Locate the imported thread associated with the original native session and attempt to continue it. That attempt also fails.

It is unknown whether importing was explicitly initiated or performed automatically.

Version

Affected desktop app: 0.0.46-nightly.20261008.2819. The separately invoked triage CLI reported 0.0.45. The affected desktop logs and database used the nightly version and statev2.sqlite.

Environment

macOS, Darwin 25.5.0, arm64; T3 Code Nightly desktop app with local server; provider claudeAgent; model claude-opus-5-5; triage Node.js v26.8.2.

Evidence

Identifiers below are synthetic.

Pending fork transfer <TRANSFER_ID> has no resolvable source provider thread.

Continuing the imported thread produced:

ProjectionStoreApplyEventError:
Failed to apply orchestration projection event provider-turn.updated.

UNIQUE constraint failed:
orchestration_v2_projection_provider_turns.provider_thread_id,
orchestration_v2_projection_provider_turns.ordinal

The original thread retained 80 message records. The imported thread retained 75. The original native Claude session file also existed.

Related issues

#2256 describes a similar visible-history/context-loss symptom, but does not establish this provider-association collision or the accompanying fork and uniqueness failures. No confirmed duplicate was identified.

Fix applied or workaround

Backed up the native Claude session and exported the original thread’s 80 saved messages. A fresh Claude thread read the complete transcript and successfully summarized the previous decisions and outstanding work.

This recovered written context without directly editing the database. It did not repair the original thread association or restore internal reasoning/tool state.

Filed by

Prepared by GPT-6 Astra in Codex via t3 triage.

Activity

  1. juliusmarminge commented on Oct 8, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the detailed write-up. The synthetic DB state you describe lines up with what main does today (checked at a6ec88f7a). The likely cause is that the bulk importer builds the same provider-thread id the live Claude adapter already used for that session, and the projection upsert then moves the record over to the imported thread.

    How P likely changed owner

    • The bulk importer (thread id import:<instance>:<session>, apps/server/src/project/AgentSessionImporter.ts:226-228) only checks for an existing thread under that import: id (:252-266). It never checks whether a native thread already owns the session. That's the gap tracked in Imported agent sessions duplicate T3-native threads: import dedup never consults the native thread namespace #10933.
    • It derives the provider-thread id from the driver and native session id without the provider instance (AgentSessionImporter.ts:270-273). The native Claude adapter derives it the same way (Adapters/ClaudeAdapterV2.ts:1113-1116), so both produce the same P (IdAllocator.ts:216-232). By contrast, the per-session import path includes providerInstanceId (Orchestrator.ts:2241-2245), so it shouldn't collide.
    • The import then writes provider-thread.updated with appThreadId set to the imported thread (AgentSessionImporter.ts:312-318, :352-375). The projection upsert is ON CONFLICT(provider_thread_id) DO UPDATE SET thread_id = excluded.thread_id, … (orchestration-v2/ProjectionStore.ts:2193-2206), so P's thread_id/payload_json silently switch to import:claudeAgent:…. That matches what you saw.
    • On main, the only client that calls agentSessions.import seems to be the welcome wizard's import step (apps/web/src/components/onboarding/WelcomeWizard.tsx:1215), and visiting /welcome reopens it (apps/web/src/routes/welcome.tsx:13). So my best guess is that onboarding or import was re-run for a project that already had native Claude threads. I haven't confirmed that.

    How that explains the three symptoms

    • Context loss (run 20): a thread's provider threads are loaded by thread_id (ProjectionStore.ts:3020-3038). Thread A still has activeProviderThreadId = P, but P no longer appears in its projection, so activeProviderThread is undefined (Orchestrator.ts:4842-4844). The next send then mints a new pending:<runId> provider thread (Orchestrator.ts:5221-5226), which means a new native Claude conversation with no handoff.
    • Fork failure: providerThreadForRun searches only that same per-thread list (Orchestrator.ts:640-647). For run 19 it returns undefined, which trips the guard at Orchestrator.ts:5569-5574.
    • UNIQUE error on the imported thread: the next provider-turn ordinal is computed only from the current thread's providerTurns (ProviderTurnStartService.ts:1248-1254). The imported thread has none under P, so it picks ordinal 1, but A's turns already hold (P, 1…). That conflicts with the unique index on (provider_thread_id, ordinal) (persistence/Migrations/055_OrchestrationV2.ts:178).

    Origin: the v2 importer that emits this deterministic id arrived with the new orchestrator in #2829. The v1 importer from #5362 created duplicates (#10933) but didn't share provider-thread records.

    Related work

    Possible fix directions (untested)

    1. Land a native-ownership guard like the one in fix(server): skip native sessions during bulk import #15634, plus an instance-scoped importer id.
    2. As a safety net, have the projection upsert refuse to change thread_id on an existing provider thread that other threads' runs still reference.
    3. Add a one-off repair that gives P back to the thread whose runs reference it, and detaches or retires the colliding imported thread.

    Workaround (not verified): don't continue or fork the imported thread, since that keeps hitting the unique constraint. I haven't found an in-app action that hands P back to A. A projection rebuild would most likely replay the import event after A's events and land in the same state. For now, your approach (export the transcript and have a fresh thread read it) seems to be the safest way to recover.

  2. added
    bugSomething is broken or behaving incorrectly.
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions