fix(run): stop Start/Resume from silently no-oping on an idle lane session
spawnRun adopted any existing `ccam-lane-<id>` tmux session without looking
at it, so a Resume issued while the session sat at a bare shell prompt (left
by `ccam lanes shell`, or by a `claude` that had already exited) dropped the
whole argv: no `--resume` ran, no initial prompt was typed, and the API still
answered 200. The lane's DB `run_id` is not set in that case, so the ERUNLIVE
guard in the start route never saw it either.
Reuse the pane instead of erroring: when the session exists and
`#{pane_current_command}` is a shell, type the argv into that pane and record
the run. A pane running a program (a live `claude`, an editor, a build) is
still adopted untouched, so attaching shows what is running rather than typing
over it. `sendCommand` POSIX single-quotes every argument and uses
`send-keys -l`, the only place in tmux.js that composes a command line.
This commit is contained in:
@@ -58,15 +58,46 @@ describe("pty-run", () => {
|
||||
assert.ok(newSessionCall.includes("opus"));
|
||||
});
|
||||
|
||||
it("spawnRun is a no-op (adopts) when the tmux session already exists", () => {
|
||||
it("spawnRun is a no-op (adopts) when the existing session's pane runs claude", () => {
|
||||
const calls = [];
|
||||
tmux.__setExecImpl((args) => {
|
||||
calls.push(args);
|
||||
if (args[0] === "display-message") return "claude\n";
|
||||
return ""; // has-session succeeds → already running
|
||||
});
|
||||
const handle = pty.spawnRun({ laneId: 7, cwd: "/tmp/repo" });
|
||||
assert.equal(handle.id, "ccam-lane-7");
|
||||
assert.ok(!calls.some((c) => c[0] === "new-session"), "must not create a duplicate session");
|
||||
assert.ok(!calls.some((c) => c[0] === "send-keys"), "must not type over a live agent");
|
||||
});
|
||||
|
||||
it("spawnRun types the argv into an existing session idling at a shell prompt", () => {
|
||||
const calls = [];
|
||||
tmux.__setExecImpl((args) => {
|
||||
calls.push(args);
|
||||
if (args[0] === "display-message") return "bash\n";
|
||||
return ""; // has-session succeeds → session exists, pane is a shell
|
||||
});
|
||||
pty.spawnRun({ laneId: 8, cwd: "/tmp/repo", resumeSessionId: "abc12345" });
|
||||
assert.ok(!calls.some((c) => c[0] === "new-session"), "must not create a duplicate session");
|
||||
const literal = calls.find((c) => c[0] === "send-keys" && c[3] === "-l");
|
||||
assert.ok(literal, "expected a literal send-keys with the command line");
|
||||
assert.match(literal[4], /^'claude' .*'--resume' 'abc12345'$/);
|
||||
assert.ok(
|
||||
calls.some((c) => c[0] === "send-keys" && c[3] === "Enter"),
|
||||
"expected the command to be submitted"
|
||||
);
|
||||
});
|
||||
|
||||
it("spawnRun single-quotes an initial prompt typed into an existing shell pane", () => {
|
||||
let literal = null;
|
||||
tmux.__setExecImpl((args) => {
|
||||
if (args[0] === "display-message") return "zsh\n";
|
||||
if (args[0] === "send-keys" && args[3] === "-l") literal = args[4];
|
||||
return "";
|
||||
});
|
||||
pty.spawnRun({ laneId: 9, cwd: "/tmp/repo", initialPrompt: "don't; rm -rf /" });
|
||||
assert.ok(literal.endsWith(`'don'\\''t; rm -rf /'`), literal);
|
||||
});
|
||||
|
||||
it("spawnRun with resumeSessionId passes --resume in argv", () => {
|
||||
|
||||
Reference in New Issue
Block a user