fix(remote): address review feedback on omp/claude respawn continuity

- 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>
This commit is contained in:
timkjr
2026-09-07 22:11:54 -05:00
co-authored by Claude Sonnet 5
parent 88243e9ffa
commit 797f0d387c
6 changed files with 206 additions and 32 deletions
+22 -7
View File
@@ -778,19 +778,34 @@ export function buildRemoteLaunchCommand(options: {
modeCommand = override;
} else if (mode === 'claude') {
// Deterministic conversation pinning for SSH-remote claude (mirrors the
// docker-claude shape in claudeDockerPaneCommand): the FIRST run creates
// the conversation under --session-id <sessionId>; a respawn / reattach
// re-runs the same idempotent command, --session-id exits non-zero
// ("already in use"), and the `||` fallback RESUMES that same
// docker-claude shape in claudeDockerPaneCommand, INCLUDING the distinct
// resumeId branch it declares — this used to only mirror the same-id
// fallback shape, silently dropping an explicit resumeSessionId that
// differs from sessionId, e.g. a resume-from-history launch): the FIRST
// run creates the conversation under --session-id <sessionId>; a respawn
// / reattach re-runs the same idempotent command, --session-id exits
// non-zero ("already in use"), and the `||` fallback RESUMES that same
// conversation. Without a pinned id, every reattach relaunched a bare
// `claude` and started a NEW conversation (found live 2026-08-29: remote
// claude ctrl-d / ctrl-c relaunched a fresh session). A per-host
// `commands.claude` override stays authoritative (admin's explicit
// choice) and skips this entirely.
const permFlags = buildClaudePermissionFlags(claudeMode, allowedTools);
modeCommand = remoteLoginShellCommand(
`claude${permFlags} --session-id ${sessionId} || claude${permFlags} --resume ${sessionId}`
);
const cmd = `claude${permFlags}`;
// Defense in depth, mirroring claudeDockerPaneCommand's own belt-and-braces check:
// sessionId is server-minted and always safe in practice, but this command is built
// as a single shellescaped string and then executed as shell code on the remote
// host, so an unsafe value here is validated rather than trusted.
if (!RESUME_ID_SAFE.test(sessionId)) {
modeCommand = remoteLoginShellCommand(cmd);
} else {
const rid = resumeSessionId && RESUME_ID_SAFE.test(resumeSessionId) ? resumeSessionId : undefined;
modeCommand = remoteLoginShellCommand(
rid && rid !== sessionId
? `${cmd} --resume ${rid} || ${cmd} --session-id ${sessionId}`
: `${cmd} --session-id ${sessionId} || ${cmd} --resume ${sessionId}`
);
}
} else if (mode === 'omp') {
// Remote OMP respawn must RESUME the same conversation instead of
// relaunching fresh (found live 2026-08-29: remote ctrl-c/ctrl-d relaunched