Repository navigation
Drain an older terminal host in the background, or restart its terminals now - #374
Merged
Merged
Conversation
…als now (#372) A terminal host keeps the code it started with, node-pty included, until it exits, and the app never stops the daemon, so a fix in the host (#370's pty leak) reached a machine only after a full stop. The host generation is now bumped whenever a running host has to be replaced, and a daemon drains a host of an older generation it can still speak to instead of adopting it: that host keeps the terminals it has, gets no new ones, and is shut down when its last one ends. The app shows a card while one drains. Later hides it; Restart terminals (or `kelpi daemon restart-old-terminals`) restarts those panes on the current host: each shell starts again where its old one was, its Claude or Codex session is resumed, and the old host is waited out so what it leaked is free before anything new spawns. `kelpi daemon status` lists both hosts. Generation 2 starts with this change, so every machine running an older host drains it after the update. A test now fails if node-pty moves without a new generation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #372
Problem
A terminal host keeps the code it started with, node-pty included, until it exits. The app never stops the daemon, and an update or
kelpi daemon restarthands every terminal to the host already running. So a fix inside the host, like #371's pty leak, only reaches a machine after a full stop, which ends every shell.Change
Host generations
HOST_PROTOCOL_VERSIONgoes from 1 to 2. It is now the host's generation, bumped whenever a running host has to be replaced.DRAINABLE_HOST_PROTOCOLSlists the older generations a daemon drains rather than ends, each with the reason the user is shown. Generation 1 is there because it can leak a pty for every terminal it opens.host-generation.test.tsfails if node-pty's version changes withoutHOST_NODE_PTY_VERSION, and its message says to bump the generation.Draining (
boot/compose.ts)TerminalHostClient.onEmpty), the daemon shuts it down.Restarting old terminals now
restart-old-terminalsis a new wire verb (explicit chain, owner-only for paired devices). It:lsofand saves it to the pane;claude --resume <id>orcodex resume <id>.Only one runs at a time (a second request joins the first), a handoff or stop waits for it to finish, and it is refused when the daemon has no current host (terminals running in-process).
Telling the user
old-terminal-hosts, is sent on change and after every snapshot. It drives a card in the toast corner saying how many terminals and agent sessions are on the old host, why it is being retired, and what restarting costs.kelpi daemon status(andkelpid status,--jsontoo) lists the current host and each old one.kelpi daemon restart-old-terminalssays what it restarts and asks first at a terminal;--forceskips the question.terminal_hostsblock.Host version
Every released host reports its version as
0.3.0-dev: only the daemon getsKELPID_VERSION, so the host falls back to the compiled default. New hosts getKELPID_VERSIONpassed through. The card and status leave out an old host's version, since it would be wrong.Docs
docs/terminal-host.md: §2 (H = 2), §8 (a draining host dying, forced stop), §10, §11 rewritten (generations, draining, restarting old terminals, telling the user), §12 tests.docs/wire-protocol.md: the new verb (§6.8.1), the allowlist and the ping block.Testing
main(generation 1) with several terminals and a Claude session, handed over with SIGUSR2 to a dev instance of this branch on the same state. It worked.--resume <id>in its folder, and the old host exited.kelpid restart-old-terminalsdid the same for 3 terminals, and a second run said there was nothing to do.boot/terminal-host.test.ts, with real host processes and a generation 1 host built by rewriting the constant: draining across two handoffs with a new pane going to the current host; a restart into the live folder with the resume typed once; a handoff in the middle of a restart, and two restarts joining; refusing with no current host; retiring at boot; a draining host crashing; an undrainable generation ended.host.test.ts: live counts andonEmpty; a mismatched protocol refused.main-restart-old.test.ts: the prompt, yes and no,--force, scripts, nothing to do, status text and JSON.host-generation.test.ts, the agent event mute, ping, the bridge, the palette, the card copy and the allowlist count.pnpm check: root 646 files / 10312 tests and shell 1057 tests passed. One run hitEADDRINUSEintailnet-binding.test.ts, which passed 3/3 on its own.node scripts/verify.mjs --full: typecheck, root tests, shell tests, bundles, the full audit and the packaged smoke (74/74; ping showsterminal_hostswith generation 2) passed.plugin-pane-search: "a daemon restart stands the placement down while disconnected" (selection null).origin/main(33/34), so it was red before this change. It looks like plugin-pane-chrome fails its daemon-restart check since #340: the first click after the reconnect, on Settings ▸ Plugins, is consumed as a window activation #348's cause.Reviewer notes
A fresh Claude session and Codex reviewed the branch. Both flagged a handoff or stop arriving during a restart; both now wait for the restart to finish. Also fixed from the reviews:
lsoflookups in parallel;Not covered
🤖 Generated with Claude Code