Skip to content

fix(sessions): stop killing slow-booting backends on the readiness timeout - #776

Merged
Juliusolsson05 merged 5 commits into
mainfrom
fix/wake-readiness-no-kill
Sep 5, 2026
Merged

Juliusolsson05 merged 5 commits into
mainfrom
fix/wake-readiness-no-kill

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 4, 2026 •

Copy link
Copy Markdown
Owner

Problem

ensureSessionLive treated "the backend did not report input-ready within 30 s" as "the backend failed to start": it killed the process and marked the pane failed (session.ts readiness branch). On a restart with a dozen CLIs starting at once, opencode TUIs (readiness = first PTY byte + 250 ms) and Codex resumes of large rollouts take longer than that. Lifecycle journal, run 75117: kill.request cause=live-entry at 35.6–35.8 s ×3 and at 69.5 s in run 95737 — against processes that were alive (no exit in between); the same sessions re-woken by hand became ready in 1.7–4.1 s. The pane then sat dead until the user retried it, observed 94 s, 217 s and 556 s after boot. This is the "resuming a terminal on boot is like a 3 min process" and "opencode terminal resume is fucked up" reports (#772; investigations in the boot audit and the opencode report).

Two amplifiers: AgentTerminalLeaf wakes on mount while the rehydrate recovery is already in flight, main joined that recovery but still reported disposition: 'spawned', so the leaf's wake inherited the kill timer; and the leaf retried its attach only after that wake resolved, so a raw terminal pane was blank for the whole TUI boot even when it succeeded.

Behaviour

  • A readiness timeout never kills a live backend. waitForSessionInputReady already rejects immediately for a backend that exited or failed, so reaching the deadline means the process is alive and slow. The wake now resolves ok, the pane keeps its status and stays not-ready (readiness arrives whenever the composer does), and the journal records wake.result ok:true code:'ready-timeout'. An observed exit or start failure still fails the wake and kills a spawned backend.
  • Joining is not spawning. SessionManager.recover returns disposition: 'joined' when it dedupes onto an in-flight claim (SessionRecoverResult widened); the renderer treats joined + live like adopted and skips the wait. The leaf therefore attaches as soon as the recovery it joined resolves.
  • The raw terminal never waits. ensureSessionLive takes { awaitInputReady?: boolean }; AgentTerminalLeaf passes false at both wake sites. providerRuntime === 'terminal' sessions skip the wait everywhere.
  • rehydrate's own 30 s deadline on the recover RPC is unchanged: its consequence is a cancelled claim and a retryable pane, not a kill.

Design decisions

  • Not raising the constant. A longer deadline still kills at some load; the destructive consequence was the bug, not the number. The "accepted cost" note already present in the code (a wedged provider now sits not-ready instead of being self-healed) applies to this class too and is stated there.
  • joined over renderer-side detection: only main knows whether a recover call started the process; the disposition is the honest signal, and it makes the leaf's attach follow backend liveness for free.
  • Wake single-flight ignores the option: a terminal pane joining a composer's wake resolves when that one does; since the wait can no longer kill, joining a waiting wake costs latency, never the backend.

Linked issues

Fixes #772. Refs #774 (spawn herd), #775 (boot-path I/O), #596, #548.

Verification

  • sessionRecovery.renderer.test.tsx (+3): a spawned backend alive but not ready past the deadline → wake resolves, killOwnedSession not called, pane started/not-ready; a backend that exits before ready → wake rejects, kill issued, pane failed; terminal-runtime and awaitInputReady: false wakes resolve without the wait.
  • sessionManager.wake.test.ts: the second wake that joins an in-flight recovery gets disposition: 'joined'.
  • AgentTerminalLeaf.dimensionOwnership.renderer.test.tsx (+1): a spawning backend is woken with { awaitInputReady: false }.
  • --project renderer src/renderer/src/workspace/hook/actions/ + the leaf suite — 9 files, 41 tests passed; --project unit wake + rehydrate — 6 passed; npx tsc -b — clean.

Limitations / follow-ups

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa

Current integration verification (2026-09-04)

Integrated merged main feb6690 without discarding the dispatcher/WebGL/engagement/resize tests. Head 87ca7e9 passes both full host CI gates. Focused checks: 14 renderer tests plus six session-manager wake tests. Original live app and Bringdown were not restarted. Ready for owner merge confirmation.

Juliusolsson05 and others added 5 commits September 3, 2026 17:24
…ing backends

Refs #772

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa
…meout

ensureSessionLive treated "not input-ready within 30 s" as "failed to
start" and killed the process. Under a restart herd an opencode TUI (whose
readiness is first PTY byte + 250 ms) or a Codex resume of a large rollout
takes longer than that; the lifecycle journal shows kill.request at 35.6 s
×3 and 69.5 s after boot against processes that were alive, and the same
sessions re-woken by hand became ready in 1.7–4.1 s. The pane then sat
dead until the user retried it — observed 94 s, 217 s and 556 s after
boot: the "3-minute boot" and "opencode resume is broken" reports.

The wait already rejects immediately for a backend that exited or failed,
so reaching the deadline means the process is alive and slow. A timeout
with a live backend now resolves the wake, leaves the pane not-ready and
journals wake.result code:'ready-timeout'; only an observed exit or start
failure keeps the kill. Main reports disposition 'joined' when a wake
attaches to a recovery another caller started, which the renderer treats
like adopted, and the raw terminal pane opts out of the readiness wait
entirely so it shows the TUI booting instead of a blank box. The terminal
provider runtime never waits.

Fixes #772
Refs #774, #775, #596, #548

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kk16MNVJtWAnCeGxGRuHqa
Two recover-suite cases still asserted that a joiner sees 'spawned';
under #772 the joiner reports 'joined' so the renderer can skip the
readiness wait for a start it did not own. Order is deterministic
(synchronous claim, then join), and both cases keep asserting exactly
one spawn.
…hput fixes

Preserve both the readiness regression and all dispatcher, WebGL, engagement and resize coverage when resolving the shared test-file conflict. Focused renderer checks pass (14 tests).

Refs #772
Juliusolsson05 added a commit that referenced this pull request Sep 5, 2026
…epair

Preserve both overlapping regression suites and stack on #776 so the integration is tested before either main merge.

Refs #752
@Juliusolsson05
Juliusolsson05 merged commit 15c41b2 into main Sep 5, 2026
2 checks passed
@Juliusolsson05
Juliusolsson05 deleted the fix/wake-readiness-no-kill branch September 5, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(sessions): restart kills healthy opencode terminal sessions that take longer than 30 s to become input-ready

1 participant