Skip to content

fix(tmux): self-heal --continue REPL that dies on unresumable transcript - #612

Open
olegbrok wants to merge 1 commit into
mainfrom
fix/tmux-continue-unresumable-fresh-retry
Open

fix(tmux): self-heal --continue REPL that dies on unresumable transcript#612
olegbrok wants to merge 1 commit into
mainfrom
fix/tmux-continue-unresumable-fresh-retry

Conversation

@olegbrok

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes the crash-loop that hit Barsik when switched from the SDK transport to tmux: a tmux claude --continue exits immediately when the newest transcript for cwd isn't interactive-resumable (classic trigger: an SDK-origin transcript left behind after an SDK→tmux transport switch).
  • The detached session has no remain-on-exit, so tmux auto-reaps it — but new-session already returned rc=0, leaving the state machine CONNECTED against a dead REPL and warm-wakes crash-looping forever.
  • _has_prior_transcript (tmux transport: claude --continue exits 1 on cold-start when no prior transcript exists for cwd #511) can't catch this: it only checks that some *.jsonl exists, not that the newest one is interactive-resumable.

Fix

  • Add a post-launch liveness check (_verify_repl_survived_launch): poll has-session over a short grace window (_REPL_LIVENESS_GRACE_SEC = 2.0, poll 0.25s). Only paid on --continue launches — healthy fresh boots cost nothing; a dead REPL early-returns fast.
  • When the --continue REPL died on launch, retry once with a forced-fresh launch (force_fresh_context_once) so the agent comes up on a new transcript instead of wedging. A genuine cold-start failure (dead even fresh, or timeout) still raises.
  • Format-agnostic — does not try to discriminate SDK vs interactive transcript formats (that heuristic proved unreliable; SDK transcripts also carry cwd records).

Test plan

  • 3 new tests: relaunch-fresh-on-death, raises-when-dead-even-fresh, fresh-launch-skips-liveness-retry
  • Made mock tmux has_session dynamic; HOME-isolated the two concurrency dedup tests so they stay on the fresh path
  • pytest tests/test_tmux_session.py → 220 passed
  • ruff check clean

🤖 Generated with Claude Code

A tmux `claude --continue` exits immediately when the newest transcript
for cwd isn't interactive-resumable (classic trigger: an SDK-origin
transcript left behind after an SDK→tmux transport switch). The detached
session has no remain-on-exit, so tmux auto-reaps it — yet `new-session`
already returned rc=0, leaving the state machine CONNECTED against a dead
REPL and warm-wakes crash-looping forever.

`_has_prior_transcript` can't catch this: it only checks that *some*
*.jsonl exists, not that the newest is interactive-resumable. Add a
post-launch liveness check (poll has-session over a short grace window,
only on --continue launches) and, when the REPL died, retry once with a
forced-fresh launch so the agent comes up on a new transcript instead of
wedging. A genuine cold-start failure still raises.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# as a unit (REPL + tailer).
try:
await self._tmux.kill_session()
except Exception:
except asyncio.TimeoutError:
try:
await self._tmux.kill_session()
except Exception:
if not await self._verify_repl_survived_launch():
try:
await self._tmux.kill_session()
except Exception:

bradbrok commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Heartbeat maintenance (2026-06-01): rebased onto current main (ec39201 — 9 commits since PR was opened on 52b00ac, including isolation_mode/provisioning seam, per-agent signing keys, CommandRunner/UnixUserProvisioner, live token analytics, and ultracode effort tier).

Rebased cleanly — no conflicts in tmux_session.py. Force-push to fix/tmux-continue-unresumable-fresh-retry not possible from this web session (proxy returns 403 on force-push) — same constraint as #613's last heartbeat.

Tests: pytest tests/test_tmux_session.py230 passed (including the 3 new liveness-retry tests). Branch is CI-green and mergeable_state=clean so GitHub can auto-merge without a push if approved now.

🤖 Heartbeat maintenance


Generated by Claude Code

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.

3 participants