fix(omp,remote): thread remote-omp resume/continue through respawn and reattach - #362
Conversation
|
Thanks for this, and sorry it has sat. The branch is conflicting, which means GitHub has run no CI on it at all, and the conflicts are not mechanical: master has moved under two of the three things this PR does. The reconnect-watcher half is already on master, in a form that has since been fixed. #355 (your commit, same title) merged on 2026-09-04. This version's The remote omp branch calls The host-local omp resolver now feeds Smaller: The claude session pin and the trailing-slash normalisation are good and I want them. If you would rather land those quickly, |
… resumes instead of relaunching fresh Two independent defects made ANY clean exit from a remote SSH session (user ctrl-d or ctrl-c, or a dropped pane) relaunch the agent as a NEW conversation: 1. SSH-remote claude was launched as a bare `claude --dangerously-skip-permissions`, so the remote-respawn path (COD-108 reattachRemote re-running the idempotent launch command) started a fresh conversation every time. Pin it to the deterministic Codeman session id, mirroring the docker-claude shape (claudeDockerPaneCommand): `--session-id <id>` to create, with the `|| --resume <id>` fallback so the idempotent re-run resumes instead of erroring with "already in use". A per-host commands.claude override still wins. 2. OMP --resume pinning silently degraded to ambiguous `--continue` whenever a case path ended in a trailing slash (e.g. remote `remotePath` stored verbatim as `/home/user/dotfiles/`): mangleOmpWorkingDir produced `-dotfiles-` while omp persists sessions under `-dotfiles`, readdirSync returned null for an existing dir, and findLatestOmpSessionId/resolveAndClaimOmpSessionId never matched. Normalize the trailing slash before mangling (new exported stripTrailingSlash) and compare the session header cwd against the same normalized value. Both were found live 2026-08-29 on a remote OMP/Claude node: ctrl-c and ctrl-d behaved identically, both relaunching a fresh session.
The COD-108 reconnect watcher treated any dead local pane as a dropped transport and re-ran the pane command — so a normal ctrl-c/ctrl-d on a remote omp/opencode/claude auto-spawned a FRESH agent (claude only looked correct because its '--session-id || --resume' fallback resumed, with a loud 'already in use' error first). Distinguish a transport drop from an intentional exit: only reconnect when the durable remote tmux session (codeman-ssh-*) is verifiably still alive on the remote host. A clean exit tears that session down; the watcher now probes it via ssh has-session and skips (remote-gone) when it is gone OR unknown (fail closed). The probe is cached per-session and fired async so the 5s tick never blocks on ssh. Also thread ompConfig/resumeSessionId into the remote builders so a dead-pane respawn of an omp session resumes (--resume <id>) or continues (--continue) instead of launching bare omp. Tests: 3 new cases pinning remote-gone / unknown / alive decisions; remote omp resume + --continue fallback. Verified live: all three remote CLIs stay dead after exit.
- Remote omp command now renders through buildSpawnCommandFromRegistry (the mode-agnostic engine local/docker spawns use) instead of the buildOmpCommand() the CLI-registry refactor deleted. - Session._pinOmpRespawnId()/_maybeCaptureOmpSessionId() now skip host-local ~/.omp resolution entirely for a remote session and fall back to --continue: that resolver only ever reads THIS host's filesystem, which is meaningless (and could wrongly alias an unrelated local conversation) for a conversation that lives on the remote host. - Remote-claude launch now honors an explicit resumeSessionId distinct from sessionId (mirrors claudeDockerPaneCommand's shape), and validates sessionId the same way that sibling does before interpolating it into the remote shell command. - Add the still-missing header-cwd half of the trailing-slash test, and document respawn/reattach continuation + auto-reconnect-vs- clean-exit in docs/remote-sessions.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5041bc6 to
797f0d3
Compare
|
Thanks for the detailed review — rebased and addressed all four points. Reconnect-watcher half: dropped entirely, as you said. Confirmed the merge resolved every conflict in that region to master's side ( Remote omp branch: rendered through the registry now. I used Host-local omp resolver on a remote respawn: you're right, and your dotfiles example is exactly the failure case I reproduced. Smaller items: On the cherry-pick-vs-one-PR question: I looked at splitting it back out, but by the time everything's rebased and fixed, the omp threading and the claude pinning share too much of the same code path (both go through CI is green and it's rebased clean on current master. |
Each item is from the pre-merge review of the PR it names, applied on master rather than by pushing to a contributor branch. #400 (response viewer, shenlvkang-collab) - The brief view opened at `scrollTop = 0`, right when it was a single card holding the last row. Now that it renders the whole turn, the top is the turn's first narration line and the answer can be screens below it, while loadFullContext already scrolls to the bottom of the same turn. A multi-row turn now opens at its newest text; a single card still opens at the top. #401 (loopback links as web tabs, shenlvkang-collab) - Drop `*.localhost` from the auto-route set. Every other member is an address literal that can only mean this box; a `*.localhost` DNS name is not one, and a resolver with a search domain retries `evil.localhost` as `evil.localhost.<search domain>`. The link source is agent-written terminal output, so that set is the whole confinement on a tap that makes Codeman fetch a URL server-side and persist it. The page-side test stays broader (`isOnBoxHostname`), where a false positive only declines to proxy. - A link to the origin root navigated nothing: the path was flattened to '', which openWebview reads as "no deep link", leaving an open frame where it was. - `this.webviews` being set does not mean it is loaded. initWebviews() assigns a truthy empty map and only then awaits the list, so a tap during page load found nothing to reuse and POSTed a duplicate record. Join the in-flight refresh instead. - One dashboard per dev server rather than per host spelling, which is what the method's own comment already promised. - Toast on the auto-create: it writes webviews.json, broadcasts over SSE and adds a Run-dropdown row on every signed-in device, with a new tab as its only previous signal. #362 (remote omp continuation, timkjr) - Accept the allowlisted `mode === 'omp'` arm as-is; a blanket registry render would hand deepseek a locally-resolved --profile and bypass claude's own overlay. A registry-declared switch is the follow-up if a third mode needs it. - Revert the whole-file Prettier reformat of docs/remote-sessions.md (docs/ is hand-formatted and outside `npm run format`), keeping only the two new sections. - Correct three stale passages: architecture-invariants' `exec claude --dangerously-skip-permissions`, the `exec <cli>` paragraph (claude and omp now have their own arms, and the claude pane's PID is the login shell), and omp-integration's `-c 'omp'`. RemoteCommandMode gains deepseek and omp. - Add the missing `_maybeCaptureOmpSessionId` remote-guard test; the sibling guard in `_pinOmpRespawnId` had one and this path runs earlier, on the first idle turn. #388 (keyCode 229 recovery, aakhter) - Gate notifyCanonicalData on shouldSuppressTerminalQueryResponse and isTerminalFocusOrMouseReport. onData also carries the DA/DSR/CPR/OSC replies xterm answers during Ink redraws and its SGR mouse and focus reports; any of those landing between the keydown and the candidate's resolution was read as "xterm spoke for this keystroke", standing the recovery down and leaving the character dropped, worst on a busy agent pane. Reached through window.CodemanTerminalInput: the predicates live in a module IIFE that closes long before this call site, so bare references would throw into the surrounding try/catch and stop the notify from ever running. Every fix has a test that fails without it (verified by reverting each). Full gate green on the combined tree: 358 files, 6849 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged, thank you, and thanks for the thorough rework. Dropping the reconnect-watcher half entirely and then verifying the merge resolved that whole region to master's side, byte-identical test file included, is exactly the right way to handle a branch that overlapped something already landed. The host-local-resolver bug you reproduced from my dotfiles example is the one that would have bitten people quietly, and the regression test that seeds a same-mangled-path local session file and asserts it is never touched is the right shape for it. On the design question you left open: I accepted the allowlisted Three things applied on master at merge time (
Agreed on keeping this as one PR. Your point that the omp threading and the claude pinning share Shipping in the next release. |
Summary
Follow-up to #353 (OMP backend) — this is the remote-continuation threading work that was deliberately split out at the time because it can't apply without
OmpConfigexisting upstream.Two fixes, found live 2026-08-29 on a remote OMP/Claude node, both stemming from the same root cause: a dead/dropped remote pane relaunched the agent as a brand-new conversation instead of resuming.
fix(omp,remote): pin remote conversations on respawnclaudewas launched as bareclaude --dangerously-skip-permissions, so any remote-respawn (COD-108reattachRemotere-running the idempotent launch command) started a fresh conversation every time. Pinned it to the deterministic Codeman session id (--session-id <id>to create,|| --resume <id>fallback so the idempotent re-run resumes instead of erroring "already in use") — mirrors the existing docker-claude shape.--resumepinning silently degraded to ambiguous--continuewhenever a case path ended in a trailing slash (a remote case'sremotePathis stored verbatim, e.g./home/user/dotfiles/).mangleOmpWorkingDirproduced-dotfiles-while omp persists sessions under-dotfiles, so lookup never matched. Normalized the trailing slash before mangling.fix(remote): never auto-revive a remote session after a clean agent exit--session-id || --resumefallback happened to resume it, with a loud "already in use" error first).codeman-ssh-*) is verifiably still alive on the remote host, probed viassh has-session, fails closed (skips) when gone or unknown. The probe is cached per-session and fired async so the 5s watcher tick never blocks on ssh.ompConfig/resumeSessionIdinto the remote command builders so a dead-pane respawn of an omp session resumes (--resume <id>) or continues (--continue) instead of launching bareomp.Test plan
npm run typecheck— cleannode_modules/.bin/prettierin a fresh worktree — resolved bynpm install, confirmed passing after)test/remote-shared-sessions.test.ts(most directly touched) — all passing, including the omp--resume/--continuecommand-building casestest/remote-auto-reconnect.test.ts— new cases for remote-gone / remote-unknown (fail closed) / remote-alive (legitimate reconnect still works)test/omp-session-resolver.test.ts,test/tmux-manager.test.ts— trailing-slash normalization and SSH-remote claude session-id pinning🤖 Generated with Claude Code