Repository navigation
Claude JSONL transcript can disconnect while proxy/semantic stream stays live, breaking resume identity #290
Description
Summary
Claude panes can keep working through the proxy/semantic/screen channels while Agent Code receives zero committed JSONL transcript entries. In that state the UI can still show live progress, ghosts, semantic events, and screen-rendered prompts, but the durable transcript path is absent or disconnected. When the pane is later reloaded/resumed/reopened, Agent Code has no reliable provider transcript identity and can fall back to old history, blank history, or a wrong/stale providerSessionId.
This is the long-running class of bug that originally forced the ghost-record fallback work: the live channel and committed transcript channel can diverge badly enough that the app appears alive while durable resume state is already broken.
Severity
High. This can lose apparent progress after resume/reload, corrupt the user's mental model of what is saved, and amplify memory/rendering failures because the renderer leans on semantic/ghost state for live display while JSONL never catches up.
Concrete evidence from 2026-06-17 debug artifacts
Debug bundle:
/Users/juliusolsson/.config/agent-code/debug-bundles/manual/2026-06-17T11-50-56-053-a6c70f19
Bundle facts:
- Agent Code session id:
a6c70f19-e093-4d91-8339-3a7c79abb79e - Provider kind:
claude - Proxy run dir:
/Users/juliusolsson/.config/agent-code/proxy/Users-juliusolsson-Desktop-general-hackathon-bot/shell-a6c70f19-e093-4d91-8339-3a7c79abb79e/2026-06-17T11-43-53-031Z state-snapshot.jsonreports:totalEntries: 0lastJsonlEntryAt: nulltranscriptStatus: "ready"transcriptError: null_counts.entries: 0_counts.ghosts: 11_counts.semanticLog: 200streamPhase: "awaiting-tool"- pending tool:
AskUserQuestion
feed-debug.jsonlcontains active live/rendering events despite zero JSONL entries:screen_update: 63visible_rows: 13submit: 3flow_selected: 10stream_phase: 36turn_started: 8text_delta: 49tool_input_delta: 48turn_completed: 8ghost_orphan_sweep: 11
The proxy events for that run carry Claude's upstream session header:
x-claude-code-session-id: 184505cc-570f-4d45-8015-2145e839217c
But no matching root transcript file was found under ~/.claude/projects:
184505cc-570f-4d45-8015-2145e839217c.jsonl
That makes this more severe than a renderer-only stale state bug: Claude/proxy had a provider session id, Agent Code had live semantic traffic, but the committed transcript path Agent Code depends on was missing or not discoverable.
Additional observed failed runs
Later restarted run:
/Users/juliusolsson/.config/agent-code/proxy/Users-juliusolsson-Desktop-general-hackathon-bot/shell-6c04bc5d-8a8b-4ca4-9623-aad1c1ac8072/2026-06-17T11-52-57-121Z
- Proxy events file was around 66MB.
- Proxy events contained provider id:
7f09da4d-8651-4e75-bf69-233df36d6d38 ~/.claude/projects/-Users-juliusolsson-Desktop-general-hackathon-bot/7f09da4d-8651-4e75-bf69-233df36d6d38/subagentsexisted.- That directory contained
agent-*.meta.jsonfiles, but no root7f09da4d-8651-4e75-bf69-233df36d6d38.jsonlwas found during inspection.
Another later run had provider id:
7dcaab9f-0b5d-45df-bcfc-2db1b7cd5138
No matching root JSONL was found under ~/.claude/projects during the same inspection.
Important nuance: ~/.claude/projects does contain many normal .jsonl transcripts overall, so this is not simply "Claude no longer writes flat JSONL anywhere". It appears workspace/session/path/version specific.
Current workspace identity corruption is also present
A read-only scan of the current persisted workspace found:
/Users/juliusolsson/.config/agent-code/workspace.json
sessions: 75
provider ids: 62
duplicate providerSessionId groups: 8
agent records missing providerSessionId: 5
Examples of duplicate durable provider ids:
c0d44dcb-3972-4c55-9a31-a242bb482e2a -> 681d3f85-8183-4f06-8997-44aec6de27e6:claude | aa4dd3dd-982a-4c17-b4be-a7ca872728ae:codex
1a373e7d-7a66-4714-ac1e-497c849538b7 -> d4d53e1e-0354-49eb-b030-935fd9616a19:claude | 5c087ae1-1e13-4031-873a-b3f65a65f184:claude
cef609a0-8d65-4aee-8e7c-5c8c70e70745 -> 8e826a8c-135f-4655-8c10-d12d7463e16e:claude | 70417358-183a-4adb-b533-199699263806:claude
c6e490f9-09de-499e-85a5-1fe8f88fb299 -> e1815951-d8a1-4c19-ab06-7a32e0c9a7df:claude | 06f7e0d0-fc99-4f2d-8344-4149a1803af1:claude
331a16cb-3b74-41ad-85af-174433fcafd8 -> 92276c52-abe6-462f-991c-3847ca3f2c2d:claude | 473c7bcb-5eaa-45f1-8e10-7c9eaf50b955:claude
871a17a0-fa6f-4335-8206-62175b99e251 -> 5337ca6a-a110-4369-8d82-4521514f8fb0:claude | 448a18da-94a3-4dd8-b5c2-219087d350e9:claude
Examples of agent records with no durable provider id:
fcb3bedf-58ac-4721-8b2f-7ce36eff6ed2 | claude | /Users/juliusolsson/Desktop/general/hackathon-bot
847f4909-32f0-4cd7-a6ea-18a908ae2594 | claude | /Users/juliusolsson/Desktop/general
This does not prove the JSONL disappearance cause, but it proves the resume identity layer is already in a state where wrong-history or old-history behavior is plausible.
Relevant implementation facts
- Fresh Claude panes do not persist
providerSessionIdat spawn time. - For fresh Claude, the renderer currently captures
providerSessionIdonly from committed JSONL entries insession:jsonl-entries. - If committed JSONL never arrives, the pane can stay live through proxy/semantic/screen channels but never gets a durable provider id in
workspace.json. historyLoader.tsresolves Claude history as:
~/.claude/projects/<sanitized-realpath-cwd>/<providerSessionId>.jsonl
- If that root file is missing, history load returns empty, while ghost/semantic state may still make the live pane look productive.
- Soft reload is available for Claude/Codex even when
providerSessionIdis missing. In that state it resets renderer-derived state but cannot reload committed history, which can turn an already-disconnected pane into an apparent progress loss. src/main/sessions/forwarder.tsstarts subagent observation from parentjsonl-entryevents. If the root JSONL path never emits, subagent live data may exist on disk/proxy but not enter the normal committed/subagent pipeline.
Related issues and PRs
Related open issues:
- Main-process heap unbounded — full transcript + tool-output text retained in memory (681MB, 92% strings) #288: main-process heap unbounded; heap snapshot was 681MB with 92% strings.
- Regression: session startup/resume stuck at 'loading transcript' until a manual reload #283: startup/resume stuck at
loading transcript. - Resume + session search are inconsistent — sometimes show IDs, sometimes cwd names, sometimes prompts; needs to be reasoned through and redone #96: resume/session search identity surfaces are inconsistent.
- Fix feed render ownership across committed transcript, semantic live/history, ghosts, and optimistic input #172: feed render ownership across committed transcript, semantic live/history, ghosts, and optimistic input.
- chore(upstream): Claude Code 2.1.301 is newer than accepted 2.1.143 #233: Claude Code 2.1.181 newer than accepted 2.1.143; upstream compatibility needs review.
Related merged PRs / prior mitigations:
- fix(workspace): render orphaned ghost fallback entries #7: render orphaned ghost fallback entries.
- Filter sidecar Haiku flows so session title generation stops leaking into the transcript #25: filter title-generation sidecar flows leaking into transcript.
- fix: bump claude-code-headless for stale-flow watchdog #38: stale-flow watchdog.
- main: bound long-running memory growth + heap-pressure watchdog #48: main-process memory growth/heap pressure watchdog.
- debug-bundle: include proxy-events.jsonl tail in saved bundles #62: include proxy-events tail in debug bundles.
- Add soft reload agent command #65: soft reload agent command.
- fix(feed): render orphan ghosts only when JSONL stalls past proxy #66: render orphan ghosts when JSONL stalls past proxy.
- fix(workspace): prune unowned persisted sessions #70: prune unowned persisted sessions.
- perf(worktree-activity): stream JSONL transcripts instead of readFile #87: stream JSONL transcript parsing instead of readFile.
- feat(session-preview): live conversation preview in resume + prompt-search pickers #142: session preview pane and stale-preview request token fixes.
- fix(feed): keep semantic history visible while JSONL catches up #165/fix(feed): suppress committed semantic assistant duplicates #170/feat(feed): ship semantic-first rendering stack #184/fix(feed): tighten semantic render ownership #186: semantic-first rendering and ownership fixes.
- fix(workspace): hibernate detached sessions on rehydrate #258: hibernate detached sessions on rehydrate. This PR also noted duplicate
providerSessionIdrows during diagnosis. - fix(workspace): complete session-id remap (relationships + pins) at reload/switch sites (#248) #274: session-id relationship/pin remap fixes.
- feat: live subagent fleet rendering (Agent cards) #277: subagent fleet rendering. Follow-up inspection found the watcher still read full files and retained all parsed entries, which can amplify heap pressure.
Working hypotheses to investigate, not conclusions
- Claude Code may sometimes create/advertise a session id over the API/proxy before or without creating the expected root JSONL transcript file.
- Claude Code may now write some sessions under a different path/layout for certain workspaces, versions, or subagent-heavy runs. The presence of
session-id/subagents/*.meta.jsonwithout the rootsession-id.jsonlis suspicious. - Agent Code may miss a freshly-created root JSONL file if the file appears before the fresh-session tailer snapshots/watches the project directory. A local hardening patch now treats files with
mtime/ctime >= spawnStartas candidates, but this only covers a narrow race and does not explain runs where the file is absent. - Agent Code's durable identity model is too dependent on committed JSONL. The proxy header has the provider session id earlier than JSONL; that id should probably be captured as provisional durable identity, then reconciled with JSONL rather than ignored until JSONL arrives.
- Duplicate
providerSessionIdrows inworkspace.jsoncan cause wrong-history resume/preview behavior even after JSONL begins working again. Some duplicates are intentional clone/rewind/provider-switch products, but reload/rehydrate also treat the field as a unique resume identity, so this needs a stricter invariant or explicit lineage model. - Soft reload should not destructively clear a pane with no provider id unless it can preserve/replay semantic/ghost state or tell the user that no committed transcript exists.
- Heap pressure is likely a multiplier, not necessarily the root cause: large proxy events, retained tool outputs, ghost/semantic fallbacks, and subagent watcher retention can make a disconnected transcript state much worse.
Suggested investigation plan
- Build a read-only diagnostic that correlates, per live/debug session:
- Agent Code
sessionId - Claude proxy
x-claude-code-session-id - expected project dir from
realpath(cwd)+ sanitizer - root JSONL existence/mtime/size
- nested session dir existence
- subagent meta/body existence
- renderer
providerSessionId - workspace metadata owners and duplicates
- Agent Code
- Add a debug bundle section that records the expected Claude transcript path and whether it existed at capture time.
- Add main-process telemetry for fresh Claude sessions:
- when proxy first observes
x-claude-code-session-id - when tailer attaches to a file
- when first JSONL entry arrives
- when no JSONL appears after N seconds despite semantic/proxy activity
- when proxy first observes
- Review Claude Code 2.1.179/2.1.181 storage behavior against our accepted support matrix (chore(upstream): Claude Code 2.1.301 is newer than accepted 2.1.143 #233). Confirm whether transcript file layout or flush timing changed upstream.
- Audit workspace persistence for duplicate
providerSessionIdsemantics. Decide whether duplicates are illegal, legal only with explicit clone lineage, or legal but must never participate in resume lookup without disambiguation. - Guard or redesign soft reload when
providerSessionIdis absent. - Add a failing regression harness with a fake Claude/proxy session that emits semantic/proxy events but no root JSONL, then verify the UI surfaces "live but not durable" instead of silently claiming transcript is ready.
Acceptance criteria
- A fresh Claude pane that has live proxy/semantic traffic but no JSONL transcript visibly reports that the committed transcript is disconnected/not durable.
- Agent Code captures the provider session id from the earliest trustworthy source and persists it with provenance, without pretending history is reloadable until the transcript path is confirmed.
- Rehydrate/reload cannot silently resume a different/old transcript because of duplicate or stale
providerSessionIdmetadata. - Soft reload cannot erase the only visible progress for a pane that has no committed transcript path.
- Debug bundles contain enough path/session correlation to determine whether Claude did not write JSONL, wrote it somewhere else, or Agent Code failed to attach.
- Heap behavior remains bounded when JSONL is absent and semantic/ghost/proxy streams are large.
Activity
Juliusolsson05 commentedon Jun 18, 2026
Investigation update: local forensic evidence from 2026-06-17/18
This comment consolidates read-only local artifact inspection. It intentionally avoids dumping request bodies or
body_b64payloads.Primary debug bundle
Inspected bundle:
Manifest and runtime state show a live Claude pane with no committed transcript:
state-snapshot.json:feed-debug.jsonlproves live activity despite zero committed transcript entries:render-diagnostics.jsonalso agrees with the disconnected state:Proxy header evidence from
proxy-events.jsonl:The expected root transcript path was absent:
find /Users/juliusolsson/.claude/projects -path '*184505cc-570f-4d45-8015-2145e839217c*' -printreturned no matches.Neighboring runs show the same pattern
~/.config/agent-code/proxy/Users-juliusolsson-Desktop-general-hackathon-bot/shell-6c04bc5d-8a8b-4ca4-9623-aad1c1ac8072/2026-06-17T11-52-57-121Z/proxy-events.jsonl:~/.config/agent-code/proxy/Users-juliusolsson-Desktop-general-hackathon-bot/shell-97a5cb95.../2026-06-17T12-28-41-681Z/proxy-events.jsonl:Current/recent run:
Expected missing root paths checked:
All were missing during inspection.
Workspace metadata state
Read-only scan of current workspace:
Missing provider ids include live/relevant Claude records:
Memory correlation
From the primary bundle
performance/metrics-tail.jsonl:This supports the #288 relationship: heap pressure is likely an amplifier or parallel failure in main-process retained strings/proxy/transcript/subagent data, not a renderer-only problem.
Forensic conclusion
The local evidence now spans multiple runs. Agent Code receives proxy/semantic/screen activity and sees Claude provider session headers, but the expected root Claude JSONL is missing. At least one run also has a nested session directory with
subagents/but no root<providerSessionId>.jsonl, which matches upstream Claude data-loss reports closely.Juliusolsson05 commentedon Jun 18, 2026
Investigation update: upstream Claude Code correlation
Anthropic's public
anthropics/claude-codeissue tracker has several data-loss reports that are materially similar to our observed failure. This does not absolve Agent Code; we still need better detection, persistence, and UI safeguards. But it moves “Claude Code sometimes does not create or later deletes the root transcript JSONL” from speculative to strongly plausible.Closest upstream match
anthropics/claude-code#51504
Title:
Session conversation .jsonl files not written when using parallel subagents — history permanently lostKey overlap with our local evidence:
subagents/directories.<session-id>.jsonlexisted.7f09da4d-8651-4e75-bf69-233df36d6d38/case:Upstream deletion/lifecycle reports
anthropics/claude-code#60368
Title:
[BUG] Background-fleet "deleteJob" silently unlinks main session JSONL despite cleanupPeriodDays: 36500Relevant overlap:
.jsonlis deleted butsubagents/,tool-results/, etc. are preserved.anthropics/claude-code#59248
Title:
Silent retention cleanup deletes session transcripts with no warning, opt-in, or recoveryRelevant overlap:
.jsonldeletion with survivingsubagents/directories.Upstream embedded-terminal/transcript-write regression
anthropics/claude-code#67603
Title:
v2.1.173 regression: TUI writes no transcript records when CLAUDE_CODE_CHILD_SESSION is inherited from a parent claude session — silent data loss, --resume brokenRelevant overlap:
Related upstream reports worth tracking
Searches also surfaced these relevant issues:
ai-title, no message content./resumerenders an unselected session's transcript and injects synthetic messages into its JSONL.What this changes for our investigation
The leading model should now be split:
transcriptStatus: "ready"and fail to preserve the proxy-observed provider id.providerSessionIdinworkspace.jsoncan turn upstream transcript loss into wrong-history resume behavior.Immediate implications
x-claude-code-session-idas an early provider identity signal, with provenanceproxy-header.lastJsonlEntryAt === nullafter N seconds should become an explicit disconnected transcript state.Juliusolsson05 commentedon Jun 18, 2026
Investigation update: code-path findings for transcript discovery, proxy identity, and missing-file observability
This folds in the transcript/tailer and proxy-channel research tracks.
Claude transcript path assumptions
Agent Code assumes Claude transcripts live at:
Important code locations:
packages/claude-code-headless/src/transcript/ProjectDir.tsCLAUDE_CONFIG_DIRrealpath()s cwd-src/shared/runtime/projectDir.tssrc/main/sessions/historyLoader.ts<projectDir>/<providerSessionId>.jsonlsrc/main/providerSwitch/shared.tsRisk: the long-path branch is knowingly incomplete. Both helpers truncate sanitized cwd at 200 chars instead of implementing Claude's hash suffix behavior. That is probably not this
hackathon-botcase because the cwd is short andrealpathequals the visible path, but it is another transcript-discovery hazard.Fresh tailing behavior
Fresh sessions have an unavoidable spawn/attach ordering problem:
src/providers/claude/runtime/claudeSession.tscapturesfreshSessionStartedAtMsimmediately before spawning Claude.ClaudeCodeHeadless.start()attaches the JSONL tailer before terminal attach, but the PTY/process already exists by then.JsonlTailer.tailNewSessionFile()watches one project dir, snapshots existing.jsonls, and attaches to a fresh/new candidate.Recent local hardening added
freshSinceMsso files that already exist during the initialreaddircan still be treated as fresh if theirmtime/ctime >= spawnStart. That covers a narrow file-created-before-watcher race.But it does not explain the strongest evidence in this issue: several proxy-observed provider ids have no matching root JSONL anywhere in the expected project dir.
Missing-file observability gap
The biggest confirmed Agent Code gap is that “no transcript file appeared” is not a first-class state.
Current behavior:
no file selectedevent.bootstrapTail()/readNew()stat failures return without surfacing an error.jsonl-errorloses context when forwarded; it does not include file path, mode, cwd, resume id, offset, or tailing stage.while semantic/proxy/screen activity continues.
Provider id capture is currently JSONL-dependent
Fresh Claude panes do not persist
providerSessionIdat spawn time. The renderer captures Claude provider id only from committed JSONL entries, via the bulksession:jsonl-entrieshandler.That means if JSONL never appears:
workspace.jsonnever gets durable provider identityProxy already observes the session id before JSONL
The MITM proxy records the
x-claude-code-session-idheader:packages/claude-code-headless/src/proxy/mitmAddon.pyallowlists and writes the header into proxy events.ProxyServerpollsproxy-events.jsonland emits parsed raw transport events.ClaudeSessionforwards those events only intoheadless.handleProxyTransportEvent().ClaudeProxyAdapteruses request headers for attribution policy, then drops them from durable/session state.This exactly matches the local bundles:
proxy-events.jsonlhas the Claude provider id, but the renderer session metadata does not.Recommended instrumentation/fix seams
provider_session_observedevent at the proxy boundary when a request hasheaders['x-claude-code-session-id'].Suggested payload:
Forward that through
ClaudeCodeHeadless→SessionManager→ IPC → renderer.Renderer should persist this as provisional provider identity only when metadata has no provider id yet. Existing JSONL-confirmed identity should not be overwritten silently.
Keep a separate durability bit/state. Proxy-observed id means “we know Claude's session UUID”; it does not mean “history is reloadable.” History becomes reloadable only after the root JSONL exists or a supported alternate layout is confirmed.
Add a transcript watchdog:
Make missing resume file explicit. A missing
<projectDir>/<resumeSessionId>.jsonlduring resume should surface an error/diagnostic instead of silently polling nothing.Expand
jsonl-errorpayloads with:Working conclusion for this track
Agent Code cannot assume JSONL is the first or only source of provider identity anymore. The proxy observes Claude's session id early and reliably enough to use as a provisional identity signal. But JSONL existence must remain the durability gate for resume/history, because upstream Claude can apparently advertise a session id while failing to create or retaining no root transcript file.
Juliusolsson05 commentedon Jun 18, 2026
Investigation update: workspace identity and duplicate providerSessionId findings
This covers the workspace persistence / rehydrate / ownership track.
Conclusion
Duplicate
providerSessionIdvalues are not healthy workspace state unless we add an explicit alias/lineage model. The current code does not have such a model.Duplicate local
sessionIdreferences can be valid in selection-only surfaces like Tiled Dispatch lanes. That is different from two different workspace sessions sharing one durable provider transcript id.Current duplicate provider ids are mostly hibernated/detached, so they are not an immediate startup fork-bomb risk after #258. But they are unsafe dormant aliases: if revived/reloaded/attached, multiple local panes can resume or mutate the same provider transcript.
Current workspace evidence
Read-only scan of:
Summary:
Examples of duplicate durable provider ids:
Cross-provider duplicate example:
Missing provider ids include relevant Claude panes:
What is expected vs dangerous
Expected:
sessionIdselected by multiple Tiled Dispatch lanes.providerSessionIdabsent on a truly fresh, unused provider pane before the first durable provider transcript event.Dangerous:
sessionIds with the same(kind, cwd, providerSessionId).providerSessionIdafter live semantic/proxy activity, because reload/history has no durable transcript identity.Corrupted unless proven otherwise:
workspace.json. PR fix(workspace): hibernate detached sessions on rehydrate #258 noted duplicate provider ids as a separate discovered spawn/cleanup bug, not as an intentional hibernation feature.Code-path facts
providerSessionIdis the provider’s durable transcript identity, distinct from Agent Code’s launch-localSessionId.spawn()only writesproviderSessionIdwhenresumeSessionIdwas requested. Fresh panes otherwise wait for JSONL ingest.providerSessionIdback asresumeSessionIdfor live tile leaves.Invariants we should enforce
sessionskeys are workspace ownership ids. Every key must be owned by exactly one owning surface: tile leaf, detached record, or buried record. Autosave already prunes unowned metadata.dispatchMode.focusedSessionId, pins, and tiled lanes are selection pointers, not ownership. They must not preserve or resurrect sessions.For resumable agent sessions,
(provider kind, transcript path)should be unique among distinct workspace sessions. For Claude, transcript path is:For Codex, it is the exact rollout file for the thread id.
Why this matters to #290
The missing JSONL problem explains “live pane later has no committed history.” The duplicate/missing provider id problem explains how recovery/resume can then appear to jump back to an old message or wrong history: the workspace has no reliable one-to-one mapping between visible Agent Code pane and durable provider transcript.
Juliusolsson05 commentedon Jun 18, 2026
Investigation update: regression test plan
This is the proposed test plan from the dedicated regression-harness track. No files have been edited yet.
Existing test setup
unit,integration, andrenderer.vitest.config.tsaliases package source directly, so package-local tests underpackages/**/*.test.tscan run in the unit project.testing/README.mdsays new coverage should go into Vitest, not new ad-hocscripts/test-*.tsprobes.Priority 1: JsonlTailer fresh-file race
Add:
Target behavior:
tailNewSessionFileattaches to a preexisting JSONL newer thanfreshSinceMs.tailNewSessionFileignores preexisting old JSONL whenfreshSinceMsis set.tailNewSessionFilewithoutfreshSinceMsdoes not replay preexisting files, documenting the original race.Assertions:
freshSinceMs.<uuid>.jsonl.tailNewSessionFile.This should be a pure unit test using
mkdtemp, real files, and a smallwaitForhelper.Priority 2: proxy-observed provider identity without JSONL
Add:
Required product seam first: emit a small provider identity event from the proxy/header path, likely from
ClaudeProxyAdapter.handleTransportEventor one layer above it.Test cases:
Assertions:
x-claude-code-session-id: <uuid>publishes exactly one identity event.jsonl-entryis needed for provider id observation.Add renderer-side identity tests:
Test cases:
Priority 3: soft reload without providerSessionId
Current risk:
softReloadRuntimeresets renderer-derived visible state and setstranscriptStatustoreadywhen no provider id exists. That can erase the only visible progress for a disconnected pane.Add:
First extract/export a pure helper, e.g.
buildSoftReloadRuntime, so this does not need a full React hook render.Test cases:
Assertions:
streamPhase: awaiting-tooland no provider id, soft reload must not erase the only visible progress.transcriptStatus: readywithtranscriptError: null.transcriptStatus: loading.Priority 4: duplicate providerSessionId diagnostics
Add a pure helper first, likely:
Add tests:
Test cases:
Assertions:
providerSessionIdare reported as a blocking ambiguity.pruneSessionOwnership.spawnSessiononce per live local session and never chooses a session by provider id.Test priority order
Juliusolsson05 commentedon Jun 18, 2026
Review follow-up: linked agents can render the same provider conversation twice
Fresh symptom to explicitly capture: linked agents / orchestration children can become confused so the UI shows what appears to be the same conversation twice under different panes. This is not merely a visual row-rendering bug. It is consistent with the workspace identity findings above:
If two distinct local sessions point at the same durable provider transcript, the UI can render the same conversation twice and later reload/resume can appear to jump to an old/wrong point. That makes duplicate/mis-bound provider identity a first-order failure mode, not a cleanup-only concern.
Claude reviewer deltas to fold into the plan
softReloadRuntimeand spawn currently producetranscriptStatus: 'ready'when there is noproviderSessionId; for disconnected Claude panes this is the bug, not a neutral default.provider_session_observed,disconnected/unavailable, and duplicate-safe resume lookup are new contracts, not existing behavior. Tests must encode the desired behavior rather than current behavior.freshSinceMstest with an absent-file test.freshSinceMsis only a narrow race mitigation; green tailer tests must not imply the missing-root-JSONL bug is fixed.totalEntries: 0andlastJsonlEntryAt: nullmust never produce transcript statusready.<resumeId>.jsonlmust surface a diagnostic instead of silently polling forever.deriveTranscriptState(...), so the core invariant is testable without React/happy-dom.freshSinceMsand watchdog timing; avoid wall-clock/flaky watcher tests.Documentation cleanup suggested by reviewers
The issue is strong but now dossier-sized. To make it easier for implementation:
Juliusolsson05 commentedon Jun 18, 2026
Implementation slice opened:
What this slice does:
Verification on the worktree:
Known caveat:
Juliusolsson05 commentedon Jun 18, 2026
PR #293 has been updated after orchestration review.
The blocker was real: ids were marked provisional but still consumed by resume/history-load paths. Commit a820f6a fixes that by making provisional ids non-resumable and adding focused unit tests for the identity transitions.
Reverified:
Juliusolsson05 commentedon Jun 18, 2026
Corrected implementation note after review:
The orchestration agents found a real blocker: proxy-header ids were marked provisional but still consumed by resume/history-load paths. PR #293 now fixes that in commit a820f6a by making proxy-header ids non-resumable/non-history-loadable everywhere those ids are consumed.
Submodule PR #21 has been merged, and PR #293 now points at claude-code-headless main commit 6178461.
Final verification:
Juliusolsson05 commentedon Jun 18, 2026
Merged implementation slice:
The final merged version includes the orchestration review fix: proxy-header provider ids are recorded as provisional but are not used for resume/history-load. JSONL or explicit resume remains the durable source for reloadable transcript identity.
Verified before merge:
Juliusolsson05 commentedon Sep 20, 2026
Audit note (2026-09-20): the identity half of this shipped; the defensive UX cannot fire in the state this issue recorded. Worth knowing before anyone assumes the marker protects them.
Shipped (PR #293, merge
63e54562):providerSessionIdentity.tsrecords proxy-header ids withproviderSessionIdSource: 'proxy-header';hasDurableProviderSession()/resumableProviderSessionId()make those non-resumable and non-history-loadable;applyJsonlProviderSessionId()rejects a conflicting durable id;decideJsonlProviderBurst()blocks a burst proven to belong to another transcript.transcriptStatus: 'disconnected'exists end to end and renders as "transcript disconnected" (readiness.ts:75). Renderer honesty when committed truth never arrives is pinned byfixtures.deadCommittedChannel159.test.ts, which names this issue.The gap.
shouldMarkProviderSessionDisconnected(providerSessionIdentity.ts:164-172) returnsfalsewhentranscriptStatusis'loading'or'ready'— only'idle'passes. But a fresh Claude pane is seeded'ready'(seedResumedRuntimeFields:meta?.providerSessionId ? 'loading' : 'ready'),emptyRuntime()also defaults'ready'(session-runtime/state.ts:909), and nothing on main assigns'idle'on a live spawn.That is precisely the bundle in this issue:
transcriptStatus: "ready",totalEntries: 0,lastJsonlEntryAt: null. So the guard is real, the id can no longer be confused with a durable one — and the banner that was supposed to tell the user their transcript is dead is unreachable on the exact shape that motivated it.Also still unaddressed: the workspace.json duplicate-
providerSessionIdhalf, and the time-based watchdog ("after N seconds with a live proxy stream and no JSONL growth").Remaining work is therefore: make
'idle'reachable (or widen the gate to'ready'with an age condition), then the watchdog.1 remaining item
fix(claude): say so when a finished turn never commits a transcript
Juliusolsson05 commentedon Sep 25, 2026
Progress note (quality loop): #1222 ships the marker slice. After a completed Claude turn plus 15 s with zero committed entries and no durable identity, the pane shows "transcript disconnected" in both rendered and terminal view, and it clears on the first committed record.
Still open here:
providerSessionIdcleanup.turn_completed.ready, andmanagedTranscriptUnavailableReasonthen reports its transcript as available. Found by the fix(claude): say so when a finished turn never commits a transcript #1222 lifecycle review; it needs the completed-turn/no-commit evidence carried across adoption, or derived on the receiving side.fix(claude): show the disconnected transcript in terminal view; pin t…
Juliusolsson05 commentedon Sep 25, 2026
Reopened: #1222 closed this on merge even though its final commit and PR body say "Refs #290" (the PR was first opened with "Fixes #290"; see the next comment for the cause). #1222 shipped only the marker slice. Still open, per the earlier comment here: duplicate
providerSessionIdcleanup, detection for a pane still mid-turn, and keeping the marker across a window transfer or reload.Juliusolsson05 commentedon Sep 25, 2026
Cause: GitHub kept the closing link it created when #1222 was opened with "Fixes #290". Rewriting the PR body and amending the commit to "Refs" did not remove it (
closedByPullRequestsReferences: #1222). The quality loop now checksclosingIssuesReferencesbefore every merge.Juliusolsson05 commentedon Sep 25, 2026
Quality-loop evidence pass on the three remaining items (2026-09-25):
3. The marker across a window transfer or reload: mostly covered already. Evidence, not a guess:
providerSessionIdSource: 'proxy-header', persisted in SessionMeta).initialHistory.tsreloads such a pane asdisconnectedwhen there is no committed transcript: see theisProvisionalbranch, andhasDurableProviderSessioninproviderSessionIdentity.ts.jsonl-entryids. 1 has aproxy-headerid whose transcript file does not exist, and that is exactly the pane this path shows as disconnected after a reload. No Claude pane usesruntime-start.2. Detection for a pane still mid-turn (for example awaiting a tool forever): needs-evidence. Any timeout here competes with legitimately long tools (see the goal-loop hold design). It needs a recorded case where a mid-turn pane never commits.
1. Duplicate
providerSessionIdsemantics: needs-owner. The issue itself frames this as a product decision: are duplicates illegal, legal only with lineage, or legal but excluded from resume lookup? Some duplicates are intentional (clone, rewind, provider switch).docs(quality-loop): owner items — #290 duplicate semantics, ghost-log…
Juliusolsson05 commentedon Sep 27, 2026
Kept open, but it's not certain this still applies (owner decision, 2026-09-27). Since it was filed: #293 stopped an id taken from the proxy header being used to resume, and #1222 added a 'transcript disconnected' marker (finished turn + 15 s with nothing committed). Still unverified: a policy for duplicate provider session ids, mid-turn detection, and panes that never get a session id. The next step is to capture a fresh case, or confirm on current builds that it no longer happens.