You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Make session bootstrap and recovery atomic across reloads and restarts #545
Renderer reload and full application restart currently share a fragmented session bootstrap path. Rehydrate can bypass main-owned adoption, allocate new local ids, duplicate provider processes and scoped MCP credentials, drop panes after startup failure, and mark input ready before provider replay actually quiesces.
Required shape
Keep SessionManager. Add one atomic main-owned recoverSession operation keyed by the persisted local SessionId.
Renderer reload adopts an existing backend without new provider, proxy, or MCP side effects.
Full restart spawns exactly once under the persisted local id and forwards durable provider resume hints.
Concurrent recovery joins one synchronous main-owned claim.
Kill during startup cannot leak or resurrect a backend.
Failed panes and drafts remain visible and retryable.
Autosave resumes after every pane has a resolved outcome, including retained failures.
Composer readiness is provider-attested, level-triggered, versioned, and transported through SessionFeed.
Add deterministic fake-provider, fake-MCP, renderer, and cross-layer restart tests for Claude, Codex, opencode, and terminal sessions.
Scope guard
Do not rename SessionManager, add a state-machine framework, or combine the correctness change with unrelated file moves. Provider-specific durable identity acceptance can follow after atomic ownership and readiness are green.
Implementation is now underway in draft PR #548. The reviewed plan is included in that PR, and the first atomic SessionManager recovery slice is green.
What this issue is now blocked on — and it is not engineering
Stage 3 of the decomposition is collecting a corpus: using the app normally until the failure shapes repeat. No code can be written for Stages 4–6 until it exists, because the whole premise of the decomposition is that ~30 previous fixes failed by being authored from source reading against a failure nobody had recorded.
Recording is always on. No flag, no setup. It writes to ~/.config/agent-code/incidents/runs/<runId>/events.jsonl, capped at 50 MiB per run, alongside the existing crash/heap breadcrumbs.
npm run lifecycle:summarize # newest run, one phase ladder per session
npm run lifecycle:summarize -- --stalled # only sessions that tripped a diagnosis
npm run lifecycle:summarize -- --all # every retained run
It does not fix why backends go missing. That is still open and is the substance of this issue. What changed is that the next occurrence is classified rather than guessed at: delivery.reject now separates
never-owned — the renderer invented or resurrected a local id → a persistence/ownership defect
entry-lost-after-owned — main owned it and lost it → a teardown the renderer never observed
Those are two different defects in two different files, and they were byte-identical to the user until now.
It also does not consolidate the wake call sites. Making caller a required parameter of ensureSessionLive turned the compiler into a census and found 13, not the nine a grep had suggested. They are instrumented in place; which of them actually behave differently is a question for the corpus, not a guess — guessing is what produced #596 and #598.
Ready-made diagnoses the summarizer already flags
STRANDED CLAIM (recovery began, never resolved) · TRANSCRIPT LOAD NEVER ENDED (the #283 shape) · NEVER BECAME READY · LONG GATE STALL with its reason · DELIVERY REJECTED with the classification above · SUBMIT UNWOUND · WAKE STORM (many wakes collapsing onto few recoveries — the #596 remount shape).
Next step
Use it for ~a week, then run npm run lifecycle:summarize -- --stalled and build the Stage 4 catalog from what actually repeats.
Known gaps carried forward
The pane shows the coarse readiness reason. Claude's detailed verdict (replay-pending / composer-unpainted / human-draft) is collapsed to provider-not-ready before it leaves main; surfacing it means widening the SessionInputReadiness contract, which is a Stage 4 decision to be made from evidence. It is in gate.eval meanwhile.
Codex and opencode declare prompt-gate but never emit it — they latch a coarse boolean and genuinely cannot say why they are not ready. That silence is left visible rather than faked.
Captured restart failure again on 2026-08-30. The pre-restart Codex rollout is valid and discoverable on disk, but workspace persistence held providerSessionId: null; restart therefore emitted recover.request kind:codex hasResumeId:false and spawned a fresh backend. The running parent appeared current but compiled stale packages/codex-headless commit 4e10c167, so it did not contain the exact proxy header to rollout identity attachment merged via #703. The repair will prevent stale-submodule builds and verify that a fresh Codex run persists session_meta.payload.id before restart recovery.
Fixed on main and never closed. PR #548, merge f0a7792b.
Every bullet of "Required shape" landed:
Atomic main-owned recoverSession keyed by the persisted local SessionId, with a synchronous claim (src/main/sessionManager.ts, src/preload/api/session.ts, src/main/ipc/session.ts).
Adoption without side effects, join-one-claim, kill-cancels-without-leak, MCP exactly-once — each pinned individually in sessionManager.recover.test.ts: adopts without constructing a second provider (:162), joins concurrent calls under one claim (:262), registers MCP once on cold spawn and never during adoption (:478), kill cancels a blocked recovery leaving no backend (:584), a failed claim releases for retry (:389).
Provider-attested, level-triggered, versioned readiness through SessionFeed, with per-provider attestation tests for Claude, Codex and OpenCode, and monotonic revisions pinned at sessionManager.recover.test.ts:233.
The cross-layer restart test the issue asked for: sessionRecovery.integration.test.ts — cold spawn → renderer adoption → identity + readiness (:193), retained failure + retry (:291), close-during-start cancellation (:363).
3d525b9f later hardened autosave-after-resolved-outcome and removed the orphan-leaf latch that pinned autosave off.
One item is deferred, by this issue's own scope guard. It says "provider-specific durable identity acceptance can follow", and PR #548 deferred exactly that. The last comment here (2026-08-30) reports a restart failure in that bucket — Codex providerSessionId: null, attributed to a stale packages/codex-headless build rather than to the recovery ownership code. If that still needs tracking it belongs in #716–#718, not here.
Activity
Juliusolsson05 commentedon Jul 16, 2026
Implementation is now underway in draft PR #548. The reviewed plan is included in that PR, and the first atomic SessionManager recovery slice is green.
Juliusolsson05 commentedon Jul 28, 2026
Stages 1 and 2 shipped in #627 (merged 2026-07-28).
Plan:
docs/superpowers/plans/2026-07-28-session-lifecycle-observability.mdDecomposition:
docs/decomposition/agent-boot-readiness.mdWhat this issue is now blocked on — and it is not engineering
Stage 3 of the decomposition is collecting a corpus: using the app normally until the failure shapes repeat. No code can be written for Stages 4–6 until it exists, because the whole premise of the decomposition is that ~30 previous fixes failed by being authored from source reading against a failure nobody had recorded.
Recording is always on. No flag, no setup. It writes to
~/.config/agent-code/incidents/runs/<runId>/events.jsonl, capped at 50 MiB per run, alongside the existing crash/heap breadcrumbs.What #627 deliberately did NOT do
It does not fix why backends go missing. That is still open and is the substance of this issue. What changed is that the next occurrence is classified rather than guessed at:
delivery.rejectnow separatesnever-owned— the renderer invented or resurrected a local id → a persistence/ownership defectentry-lost-after-owned— main owned it and lost it → a teardown the renderer never observedThose are two different defects in two different files, and they were byte-identical to the user until now.
It also does not consolidate the wake call sites. Making
callera required parameter ofensureSessionLiveturned the compiler into a census and found 13, not the nine a grep had suggested. They are instrumented in place; which of them actually behave differently is a question for the corpus, not a guess — guessing is what produced #596 and #598.Ready-made diagnoses the summarizer already flags
STRANDED CLAIM(recovery began, never resolved) ·TRANSCRIPT LOAD NEVER ENDED(the #283 shape) ·NEVER BECAME READY·LONG GATE STALLwith its reason ·DELIVERY REJECTEDwith the classification above ·SUBMIT UNWOUND·WAKE STORM(many wakes collapsing onto few recoveries — the #596 remount shape).Next step
Use it for ~a week, then run
npm run lifecycle:summarize -- --stalledand build the Stage 4 catalog from what actually repeats.Known gaps carried forward
replay-pending/composer-unpainted/human-draft) is collapsed toprovider-not-readybefore it leaves main; surfacing it means widening theSessionInputReadinesscontract, which is a Stage 4 decision to be made from evidence. It is ingate.evalmeanwhile.prompt-gatebut never emit it — they latch a coarse boolean and genuinely cannot say why they are not ready. That silence is left visible rather than faked.Juliusolsson05 commentedon Aug 31, 2026
Captured restart failure again on 2026-08-30. The pre-restart Codex rollout is valid and discoverable on disk, but workspace persistence held
providerSessionId: null; restart therefore emittedrecover.request kind:codex hasResumeId:falseand spawned a fresh backend. The running parent appeared current but compiled stalepackages/codex-headlesscommit4e10c167, so it did not contain the exact proxy header to rollout identity attachment merged via #703. The repair will prevent stale-submodule builds and verify that a fresh Codex run persistssession_meta.payload.idbefore restart recovery.Juliusolsson05 commentedon Sep 20, 2026
Fixed on main and never closed. PR #548, merge
f0a7792b.Every bullet of "Required shape" landed:
recoverSessionkeyed by the persisted local SessionId, with a synchronous claim (src/main/sessionManager.ts,src/preload/api/session.ts,src/main/ipc/session.ts).sessionManager.recover.test.ts: adopts without constructing a second provider (:162), joins concurrent calls under one claim (:262), registers MCP once on cold spawn and never during adoption (:478), kill cancels a blocked recovery leaving no backend (:584), a failed claim releases for retry (:389).sessionManager.recover.test.ts:233.sessionRecovery.integration.test.ts— cold spawn → renderer adoption → identity + readiness (:193), retained failure + retry (:291), close-during-start cancellation (:363).3d525b9flater hardened autosave-after-resolved-outcome and removed the orphan-leaf latch that pinned autosave off.One item is deferred, by this issue's own scope guard. It says "provider-specific durable identity acceptance can follow", and PR #548 deferred exactly that. The last comment here (2026-08-30) reports a restart failure in that bucket — Codex
providerSessionId: null, attributed to a stalepackages/codex-headlessbuild rather than to the recovery ownership code. If that still needs tracking it belongs in #716–#718, not here.