Skip to content

fix(restart): keep watching a CLI that is slow to quit, and hand over the resume line - #919

Merged
eneskirca merged 1 commit into
mainfrom
fix/restart-late-exit
Sep 24, 2026
Merged

eneskirca merged 1 commit into
mainfrom
fix/restart-late-exit

Conversation

@eneskirca

Copy link
Copy Markdown
Owner

Fixes #899.

What happened. "Restart agent and shell" on a long-running Claude node (a cron session open for 19 h, 28 MB transcript) reported:

Restart failed: the pane did not return to a shell in time, so the CLI was not relaunched. Nothing was killed — check the pane.

The pane then showed Claude's own farewell and a clean shell prompt. So the CLI did quit, a moment after performExitPhase stopped watching at RESTART_EXIT_TIMEOUT_MS (6 s). Nothing was left to send the resume line, and the node was left at a bare shell with no agent in it.

Why the exit poll, not the detection, is the likely failure. Once claude quits, #{pane_current_command} becomes the shell, and isShellCommand accepts it on the very next poll. The pane query is a local tmux display that takes milliseconds. What remains is a CLI that took longer than 6 s to quit. Not measured: how long that exit really took, and why a large session is slower to quit.

Change

  • performExitPhase takes an optional lateExitMs. After the base window it keeps polling, but only while the pane still reads. A pane it cannot see ends the run exactly as before. A query that wedges inside the base window still lapses at the base deadline. When the option is absent, behavior is byte-identical.
  • Both user-asked restart paths use RESTART_LATE_EXIT_MS (60 s):
    • performRestartResume, by default.
    • The restart-shell branch in TerminalNode, which is the action from the report.
  • Not changed: the Eco sweep and Pause keep the bare 6 s. The sweep is serialized across the canvas and must not stall on one node.
  • The exit-timeout notice (exitTimeoutNotice) names the window actually waited. It also carries the bare resume line (claude --resume <id>) for a CLI that quits after even the late window. A custom agent with no resume line gets "Check the pane." instead of a guessed command.

Tests (agent-restart.test.ts)

  • New cases:
    • A late quit after the base window is resumed.
    • A CLI that never quits is still reported, and only after the late window runs out.
    • An unreadable pane does not spend the late window.
    • performExitPhase has no late window unless the caller asks for one.
    • Two cases for the notice text.
  • Three existing "never quits" cases now advance past the late window as well.
  • Mutations:
    • Dropping the performRestartResume default turns 2 cases red.
    • Dropping the pane === null stop turns 2 cases red, including the existing wedged-query case.
  • npm run typecheck is clean. Full suite on Linux: 12886 passed, 53 skipped.

Trade-off. The node stays held by guardConcurrentRestart for the whole wait, which is deliberate: a background watcher would release it, and a second /exit or restart could then collide with a late resume. The cost is that a node whose CLI never quits holds a bulk restart for up to 66 s instead of 6 s. Bulk restart only picks idle nodes, and those normally quit in well under a second.

Not covered

  • The restart-shell branch's use of the late window has no test of its own. It is one argument at a component call site.
  • Eco and Pause can hit the same late quit. That is left for a separate change.
  • The resume delivery's first write still carries no kill-line. A user who types into the shell during the longer wait can splice into the resume line. The risk is not new, but the window is now longer.
  • No device run. A Mac check against a large, long-running session is owed.

Surfaces

  • Desktop and Server Edition: both run the same renderer code, so both are affected.
  • Relay tabs use the same closure.
  • Mobile: not applicable. It has no restart action.

🤖 Generated with Claude Code

… the resume line

Issue #899: "Restart agent and shell" on a 19-hour cron session gave up at the 6 s exit poll. The
CLI quit a moment later with nothing left watching, so the node sat at a bare shell with no agent
and no resume, under a notice that said "Nothing was killed — check the pane".

- performExitPhase takes an optional late window (lateExitMs). Past the base 6 s it keeps polling
  only while the pane still reads; a pane it cannot see ends the run exactly as before, and a
  query wedged inside the base window still lapses at the base deadline.
- Both user-asked restart paths use it (RESTART_LATE_EXIT_MS, 60 s): performRestartResume by
  default and the restart-shell branch in TerminalNode. The Eco sweep and Pause keep the bare
  window: the sweep is serialized across the canvas.
- The exit-timeout notice names the real window and carries the bare resume line
  (exitTimeoutNotice), for the case the late window still misses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@eneskirca
eneskirca merged commit ba93ba7 into main Sep 24, 2026
5 checks passed
pull Bot pushed a commit to jw5812018/nodeterm that referenced this pull request Sep 25, 2026
Version bump 0.3.13 → 0.3.14 — 14 PRs since v0.3.13.

- **Appearance**: the Liquid Glass theme (eneskirca#908), off by default; an unthemed install
  sees only the colour clean-up that came with it.
- **Windows**: persistent agent sessions go through the session host (eneskirca#916, scoped by
  eneskirca#929), and the macOS + Windows packages finally ship the Codex relay (eneskirca#839).
- **Fixes that were losing work quietly**: a restart keeps watching a CLI slow to quit
  and hands over the resume line (eneskirca#919); a new agent on an SSH project no longer waits
  on the connection before typing its launch (eneskirca#931).
- **Codex**: usage + context for SSH sessions (eneskirca#906), no empty mascot frames (eneskirca#910).
- **Also**: ⌘K ranks before the 50-row cap (eneskirca#924), every phone size report on a
  session-host session is answered (eneskirca#921, eneskirca#920), the canvas-control skill trigger stops
  claiming in-process subagent requests (eneskirca#922), and the opencode context-link export is
  bounded (eneskirca#820).

docs/release-notes/v0.3.14.md is the Highlights block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Restart on a long-running Claude node fails at the exit poll and leaves the node without an agent

1 participant