codex: scope default cancel selection to the current Claude session (#84)
Co-authored-by: VOIDXAI <VOIDXAI@users.noreply.github.com>
This commit is contained in:
parent
40d213d13f
commit
d216a5fdea
@ -924,7 +924,7 @@ async function handleCancel(argv) {
|
||||
|
||||
const cwd = resolveCommandCwd(options);
|
||||
const reference = positionals[0] ?? "";
|
||||
const { workspaceRoot, job } = resolveCancelableJob(cwd, reference);
|
||||
const { workspaceRoot, job } = resolveCancelableJob(cwd, reference, { env: process.env });
|
||||
const existing = readStoredJob(workspaceRoot, job.id) ?? {};
|
||||
const threadId = existing.threadId ?? job.threadId ?? null;
|
||||
const turnId = existing.turnId ?? job.turnId ?? null;
|
||||
|
||||
@ -278,7 +278,7 @@ export function resolveResultJob(cwd, reference) {
|
||||
throw new Error("No finished Codex jobs found for this repository yet.");
|
||||
}
|
||||
|
||||
export function resolveCancelableJob(cwd, reference) {
|
||||
export function resolveCancelableJob(cwd, reference, options = {}) {
|
||||
const workspaceRoot = resolveWorkspaceRoot(cwd);
|
||||
const jobs = sortJobsNewestFirst(listJobs(workspaceRoot));
|
||||
const activeJobs = jobs.filter((job) => job.status === "queued" || job.status === "running");
|
||||
@ -291,12 +291,18 @@ export function resolveCancelableJob(cwd, reference) {
|
||||
return { workspaceRoot, job: selected };
|
||||
}
|
||||
|
||||
if (activeJobs.length === 1) {
|
||||
return { workspaceRoot, job: activeJobs[0] };
|
||||
const sessionScopedActiveJobs = filterJobsForCurrentSession(activeJobs, options);
|
||||
|
||||
if (sessionScopedActiveJobs.length === 1) {
|
||||
return { workspaceRoot, job: sessionScopedActiveJobs[0] };
|
||||
}
|
||||
if (activeJobs.length > 1) {
|
||||
if (sessionScopedActiveJobs.length > 1) {
|
||||
throw new Error("Multiple Codex jobs are active. Pass a job id to /codex:cancel.");
|
||||
}
|
||||
|
||||
if (getCurrentSessionId(options)) {
|
||||
throw new Error("No active Codex jobs to cancel for this session.");
|
||||
}
|
||||
|
||||
throw new Error("No active Codex jobs to cancel.");
|
||||
}
|
||||
|
||||
@ -1471,6 +1471,109 @@ test("cancel stops an active background job and marks it cancelled", async (t) =
|
||||
assert.match(fs.readFileSync(logFile, "utf8"), /Cancelled by user/);
|
||||
});
|
||||
|
||||
test("cancel without a job id ignores active jobs from other Claude sessions", () => {
|
||||
const workspace = makeTempDir();
|
||||
const stateDir = resolveStateDir(workspace);
|
||||
const jobsDir = path.join(stateDir, "jobs");
|
||||
fs.mkdirSync(jobsDir, { recursive: true });
|
||||
|
||||
const logFile = path.join(jobsDir, "task-other.log");
|
||||
fs.writeFileSync(logFile, "", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(stateDir, "state.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
config: { stopReviewGate: false },
|
||||
jobs: [
|
||||
{
|
||||
id: "task-other",
|
||||
status: "running",
|
||||
title: "Codex Task",
|
||||
jobClass: "task",
|
||||
sessionId: "sess-other",
|
||||
summary: "Other session run",
|
||||
updatedAt: "2026-03-24T20:05:00.000Z",
|
||||
logFile
|
||||
}
|
||||
]
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`,
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
CODEX_COMPANION_SESSION_ID: "sess-current"
|
||||
};
|
||||
const status = run("node", [SCRIPT, "status", "--json"], {
|
||||
cwd: workspace,
|
||||
env
|
||||
});
|
||||
assert.equal(status.status, 0, status.stderr);
|
||||
assert.deepEqual(JSON.parse(status.stdout).running, []);
|
||||
|
||||
const cancel = run("node", [SCRIPT, "cancel", "--json"], {
|
||||
cwd: workspace,
|
||||
env
|
||||
});
|
||||
assert.equal(cancel.status, 1);
|
||||
assert.match(cancel.stderr, /No active Codex jobs to cancel for this session\./);
|
||||
|
||||
const state = JSON.parse(fs.readFileSync(path.join(stateDir, "state.json"), "utf8"));
|
||||
assert.equal(state.jobs[0].status, "running");
|
||||
});
|
||||
|
||||
test("cancel with a job id can still target an active job from another Claude session", () => {
|
||||
const workspace = makeTempDir();
|
||||
const stateDir = resolveStateDir(workspace);
|
||||
const jobsDir = path.join(stateDir, "jobs");
|
||||
fs.mkdirSync(jobsDir, { recursive: true });
|
||||
|
||||
const logFile = path.join(jobsDir, "task-other.log");
|
||||
fs.writeFileSync(logFile, "", "utf8");
|
||||
fs.writeFileSync(
|
||||
path.join(stateDir, "state.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
config: { stopReviewGate: false },
|
||||
jobs: [
|
||||
{
|
||||
id: "task-other",
|
||||
status: "running",
|
||||
title: "Codex Task",
|
||||
jobClass: "task",
|
||||
sessionId: "sess-other",
|
||||
summary: "Other session run",
|
||||
updatedAt: "2026-03-24T20:05:00.000Z",
|
||||
logFile
|
||||
}
|
||||
]
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`,
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
CODEX_COMPANION_SESSION_ID: "sess-current"
|
||||
};
|
||||
const cancel = run("node", [SCRIPT, "cancel", "task-other", "--json"], {
|
||||
cwd: workspace,
|
||||
env
|
||||
});
|
||||
assert.equal(cancel.status, 0, cancel.stderr);
|
||||
assert.equal(JSON.parse(cancel.stdout).jobId, "task-other");
|
||||
|
||||
const state = JSON.parse(fs.readFileSync(path.join(stateDir, "state.json"), "utf8"));
|
||||
assert.equal(state.jobs[0].status, "cancelled");
|
||||
});
|
||||
|
||||
test("cancel sends turn interrupt to the shared app-server before killing a brokered task", async () => {
|
||||
const repo = makeTempDir();
|
||||
const binDir = makeTempDir();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user