diff --git a/plugins/codex/scripts/lib/job-control.mjs b/plugins/codex/scripts/lib/job-control.mjs index ad152c157..edd354d42 100644 --- a/plugins/codex/scripts/lib/job-control.mjs +++ b/plugins/codex/scripts/lib/job-control.mjs @@ -256,9 +256,18 @@ export function buildSingleJobSnapshot(cwd, reference, options = {}) { export function resolveResultJob(cwd, reference) { const workspaceRoot = resolveWorkspaceRoot(cwd); const jobs = sortJobsNewestFirst(reference ? listJobs(workspaceRoot) : filterJobsForCurrentSession(listJobs(workspaceRoot))); + + if (reference) { + const selected = matchJobReference(jobs, reference); + if (selected.status === "queued" || selected.status === "running") { + throw new Error(`Job ${selected.id} is still ${selected.status}. Check /codex:status and try again once it finishes.`); + } + return { workspaceRoot, job: selected }; + } + const selected = matchJobReference( jobs, - reference, + "", (job) => job.status === "completed" || job.status === "failed" || job.status === "cancelled" ); @@ -266,15 +275,11 @@ export function resolveResultJob(cwd, reference) { return { workspaceRoot, job: selected }; } - const active = matchJobReference(jobs, reference, (job) => job.status === "queued" || job.status === "running"); + const active = matchJobReference(jobs, "", (job) => job.status === "queued" || job.status === "running"); if (active) { throw new Error(`Job ${active.id} is still ${active.status}. Check /codex:status and try again once it finishes.`); } - if (reference) { - throw new Error(`No finished job found for "${reference}". Run /codex:status to inspect active jobs.`); - } - throw new Error("No finished Codex jobs found for this repository yet."); } diff --git a/tests/runtime.test.mjs b/tests/runtime.test.mjs index 8f276835b..70c0e83dd 100644 --- a/tests/runtime.test.mjs +++ b/tests/runtime.test.mjs @@ -1415,6 +1415,47 @@ test("result returns the stored output for the latest finished job by default", ); }); +test("result identifies an explicitly referenced active job", async (t) => { + for (const status of ["queued", "running"]) { + await t.test(status, () => { + const workspace = makeTempDir(); + const stateDir = resolveStateDir(workspace); + fs.mkdirSync(stateDir, { recursive: true }); + + fs.writeFileSync( + path.join(stateDir, "state.json"), + `${JSON.stringify( + { + version: 1, + config: { stopReviewGate: false }, + jobs: [ + { + id: `task-${status}`, + status, + title: "Codex Task", + jobClass: "task", + createdAt: "2026-03-18T15:30:00.000Z", + updatedAt: "2026-03-18T15:30:01.000Z" + } + ] + }, + null, + 2 + )}\n`, + "utf8" + ); + + const result = run("node", [SCRIPT, "result", `task-${status}`], { + cwd: workspace + }); + + assert.notEqual(result.status, 0); + assert.match(result.stderr, new RegExp(`Job task-${status} is still ${status}`)); + assert.doesNotMatch(result.stderr, /No job found/); + }); + } +}); + test("result without a job id prefers the latest finished job from the current Claude session", () => { const workspace = makeTempDir(); const stateDir = resolveStateDir(workspace);