mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
fix(remote): classify the has-session probe by exit status, and forget it once the pane is back
#355 made the remote auto-reconnect watcher revive a dead pane only when the durable remote tmux session is verifiably still alive, which is the right rule: a clean Ctrl-C / Ctrl-D / exit tears that session down and must never relaunch a fresh agent. Its probe, though, read `has-session`'s stdout and treated an empty string as "gone". `tmux has-session` prints NOTHING on success (measured on a scratch socket: exit 0, empty stdout, the failure message goes to stderr), so every live remote session classified as gone and transport-drop reconnects were silently disabled along with the clean-exit revives. The probe now goes by exit status through a pure, unit-tested mapping (`classifyRemoteAliveExit`): 0 is alive; ssh's own 255, a timeout (`killed`, no numeric code) and a spawn failure are unknown, which the watcher already treats as do-not-revive; any other status is the remote command's and means gone (tmux's 1 for a missing session, 127 when tmux is not installed there). Two smaller things in the same area: - The cached answer was never invalidated, so after one successful reattach a stale `true` would have revived the NEXT clean exit (the original bug back after the first transport drop), and a cached `false` from a clean exit would have left a manually restarted session with auto-reconnect permanently off. The tick now forgets the cache entry whenever the pane is seen alive. - The fire-and-forget probe has a 15s timeout against a 5s tick, so an unreachable host stacked up to three ssh processes per dead session. An in-flight set caps it at one. The probe command is pinned as a literal string, and the reattach-then-clean-exit sequence is driven through the watcher in the tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg6bcATm1pNNY4kQWGwzgu
This commit is contained in:
+31
-6
@@ -390,15 +390,40 @@ export async function remoteTmuxSessionAlive(
|
||||
if (process.env.VITEST) return true;
|
||||
const command = buildRemoteSessionAliveCommand(remote, remoteSessionName);
|
||||
try {
|
||||
const { stdout } = await execAsync(command, { timeout: 15_000 });
|
||||
// has-session prints the session name on success (exit 0). Anything else is
|
||||
// a non-zero exit → the session is gone.
|
||||
return stdout.trim().length > 0;
|
||||
} catch {
|
||||
return undefined;
|
||||
await execAsync(command, { timeout: 15_000 });
|
||||
return classifyRemoteAliveExit(0, false);
|
||||
} catch (err) {
|
||||
const e = err as { code?: unknown; killed?: boolean };
|
||||
return classifyRemoteAliveExit(typeof e.code === 'number' ? e.code : null, e.killed === true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the `has-session` probe's exit status onto the tri-state the watcher
|
||||
* reads. Pure, so the mapping is unit-tested even though the probe itself is
|
||||
* VITEST-guarded.
|
||||
*
|
||||
* ⚠️ `tmux has-session` prints NOTHING on success (measured: exit 0, empty
|
||||
* stdout; the failure message goes to stderr), so the exit status is the ONLY
|
||||
* signal. An earlier version read stdout and therefore classified every live
|
||||
* remote session as gone, which silently disabled transport-drop reconnects.
|
||||
*
|
||||
* - exit 0 → the durable remote session exists → `true`.
|
||||
* - exit 255 is ssh's own failure (unreachable host, auth, proxy/jump error)
|
||||
* and a timeout arrives as `killed` with no numeric code: we learned
|
||||
* nothing about the session → `undefined`, which the watcher treats as
|
||||
* "do not revive".
|
||||
* - any other non-zero status is the REMOTE command's: tmux's 1 for a missing
|
||||
* session, or 127 when tmux is not installed there (no durable session can
|
||||
* exist without it) → `false`.
|
||||
*/
|
||||
export function classifyRemoteAliveExit(code: number | null, killed: boolean): boolean | undefined {
|
||||
if (killed) return undefined;
|
||||
if (code === 0) return true;
|
||||
if (code === null || code === 255) return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* COD-105 — build the SSH command that lists `codeman-*` tmux sessions on a
|
||||
* remote host's canonical `-L codeman` socket.
|
||||
|
||||
Reference in New Issue
Block a user