Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions plugins/codex/scripts/codex-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -685,17 +685,38 @@ function enqueueBackgroundTask(cwd, job, request) {
const { logFile } = createTrackedProgress(job);
appendLogLine(logFile, "Queued for background execution.");

const child = spawnDetachedTaskWorker(cwd, job.id);
// Publish the queued record BEFORE spawning the worker, so a fast worker always
// finds its job (previously it could start and fail with "No stored job found").
const queuedRecord = {
...job,
status: "queued",
phase: "queued",
pid: child.pid ?? null,
pid: null,
logFile,
request
};
writeJobFile(job.workspaceRoot, job.id, queuedRecord);
upsertJob(job.workspaceRoot, queuedRecord);

let child;
try {
child = spawnDetachedTaskWorker(cwd, job.id);
} catch (error) {
// Spawn failed synchronously: mark the record failed so it does not linger as
// a pending job.
writeJobFile(job.workspaceRoot, job.id, {
...queuedRecord,
status: "failed",
phase: "failed",
errorMessage: error instanceof Error ? error.message : String(error)
});
throw error;
}

// Record only the child's pid (a pid-only patch, not a stale full snapshot), so
// a cancel/cleanup arriving before the worker publishes "running" can still
// terminate it. The worker owns every subsequent lifecycle transition; this
// merges into whatever the worker has already published without reverting it.
upsertJob(job.workspaceRoot, { id: job.id, pid: child.pid ?? null });

return {
payload: {
Expand Down Expand Up @@ -851,6 +872,12 @@ async function handleTaskWorker(argv) {
throw new Error(`No stored job found for ${options["job-id"]}.`);
}

// Honor a cancellation that landed during our startup window (before we could
// publish a pid): if the record is already cancelled, do not start the task.
if (storedJob.status === "cancelled") {
return;
}

const request = storedJob.request;
if (!request || typeof request !== "object") {
throw new Error(`Stored job ${options["job-id"]} is missing its task request payload.`);
Expand Down
8 changes: 5 additions & 3 deletions plugins/codex/scripts/lib/job-control.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,13 @@ export function enrichJob(job, options = {}) {
}

export function readStoredJob(workspaceRoot, jobId) {
const jobFile = resolveJobFile(workspaceRoot, jobId);
if (!fs.existsSync(jobFile)) {
// Guarded read: the per-job file can be pruned or session-cleaned between the
// existence check and the read, so treat any read/parse failure as "absent".
try {
return readJobFile(resolveJobFile(workspaceRoot, jobId));
} catch {
return null;
}
return readJobFile(jobFile);
}

function matchJobReference(jobs, reference, predicate = () => true) {
Expand Down
Loading