Skip to content

Restart zmx sessions in place: daemon command, CLI verbs, Loop menu - #241

Merged
scgopi merged 4 commits into
mainfrom
feat/restart-sessions
Sep 1, 2026
Merged

scgopi merged 4 commits into
mainfrom
feat/restart-sessions

Conversation

@scgopi

@scgopi scgopi commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Why

There was no way to restart every zmx session in place. Stop then start is not a restart: every kill path erases the banked session id, so the next launch is a fresh agent with the goal prompt rather than a resume. The only way to get a resume was to let the sessions die without graphcode's kill running and then reboot the daemon. This is what you need after replacing zmx or a backend CLI under a dozen running loops.

What

DaemonGraphCommand.restartNode(UUID) and .restartSessions.

  • ZmxSessionLauncher.restart is kill minus everything that makes a kill final: the banked id stays (the relaunch resumes the transcript), the first-pass record stays (the loop is not re-briefed), and the name is not condemned (the reaper would otherwise take out the relaunched session). Returns false when the old session would not die, so nothing is relaunched on top of it.
  • Unattended loops are relaunched by the daemon through the existing resume-or-fresh start. Attended loops come back when a human next opens them, exactly as after a reboot. Composites restart their workers. Remote projects go through the remote kill and ensure.
  • LoopNode.sessionRestarts is bumped only on a confirmed kill. The number means nothing; the change is the daemon's word that the old session is dead, which is what the app waits for before reattaching.

CLIgraphcode node restart <project> <node-id> and graphcode sessions restart <project>, plus node restart in the remote shim.

App — Loop ▸ Restart Session and Loop ▸ Restart All Sessions… (confirmed). A mounted agent pane reads its process exiting as the loop resolving, and a retained pane keeps that callback after you switch away, so every affected surface is retired before the daemon is asked and the open workspace is remounted only once its counter moves. The two Stop Loop cases moved into the same file to keep AppFeature under the lint budget; openNode and the history opener now share one mountWorkspace.

Verification

  • xcodebuild build and test in the worktree, swiftlint lint (0 errors), swift format lint --strict clean.
  • 6 tests added: CLI parse for both verbs; store bumps on a confirmed kill and memos it, leaves the counter alone and announces on a refused kill, refuses a finished loop, restartSessions skips finished loops; a node saved before the counter existed decodes at 0.

Not exercised end-to-end against live sessions here. The one window worth knowing about: between the workspace closing and the daemon's kill landing, opening the same loop by hand would attach to the dying session. It is under two seconds locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_0199VUTtyfuWHbu2hHXpHYw2

@scgopi
scgopi merged commit 0eef569 into main Sep 1, 2026
1 check passed
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.

1 participant