← Files Codex Process JobsARCHIVED FILE
scripts/worker.mjs
8.38 KB · Sep 30, 2026 · 23:13 UTC
#!/usr/bin/env node
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { isCliEntry } from "./cli-entry.mjs";
import { createBoundedLogWriter, resolveMaxLogBytes } from "./logs.mjs";
import { buildSpawnSpec } from "./execution.mjs";
import { getProcessIdentity } from "./process-control.mjs";
import {
TERMINAL_STATUSES,
nowIso,
readJob,
resolveJobLogs,
updateJob,
} from "./state.mjs";
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const NOTIFIER_PATH = path.join(ROOT_DIR, "scripts", "notifier.mjs");
function parseJobId(argv) {
const index = argv.indexOf("--job-id");
if (index < 0 || !argv[index + 1]) throw new Error("worker requires --job-id <id>");
return argv[index + 1];
}
export function spawnJobProcess(job, env = process.env, spawnImpl = spawn) {
const spec = buildSpawnSpec(job, env);
return spawnImpl(spec.command, spec.args, {
cwd: job.cwd,
env: spec.env,
detached: true,
shell: false,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
}
function waitForClose(child) {
return new Promise((resolve, reject) => {
let spawnError = null;
child.once("error", (error) => {
spawnError = error;
});
child.once("close", (code, signal) => {
if (spawnError) reject(spawnError);
else resolve({ code, signal });
});
});
}
export function launchUserNotification(job, env = process.env, spawnImpl = spawn, platform = process.platform) {
if (!job.notifyUser) return null;
const exitCode = Number.isInteger(job.exitCode) ? job.exitCode : "not reported";
// A label appears only when notification was an explicit choice AND the name
// was explicitly supplied. Surface-defaulted notices stay job ID, status,
// and exit code; a command-derived fallback name is never displayed, so
// paths, model names, and arguments cannot leak to a lock screen.
let message = `${job.id} finished ${job.status}; exit code ${exitCode}.`;
if (job.notifyUserExplicit && job.nameExplicit && job.name != null) {
const normalizedName = String(job.name).replace(/[\u0000-\u001f\u007f]/g, " ");
let name = normalizedName;
while (Buffer.byteLength(name, "utf8") > 512) name = name.slice(0, -1);
message = `${name} (${job.id}) finished ${job.status}; exit code ${exitCode}.`;
}
let command;
let args;
if (platform === "darwin") {
command = "osascript";
args = [
"-e", "on run argv",
"-e", "display notification (item 1 of argv) with title \"Codex Process Jobs\"",
"-e", "end run",
"--", message,
];
} else if (platform === "linux") {
command = "notify-send";
args = ["--app-name", "Codex Process Jobs", "--", "CPJ background job finished", message];
} else {
return null;
}
try {
const child = spawnImpl(command, args, {
env,
detached: true,
shell: false,
stdio: "ignore",
windowsHide: true,
});
child.on?.("error", () => {});
child.unref?.();
return child;
} catch {
return null;
}
}
async function launchNotificationRelay(job, env) {
if (job.notification?.status !== "pending" || !job.ownerThreadId) return job;
try {
const notifier = spawn(process.execPath, [NOTIFIER_PATH, "--job-id", job.id], {
cwd: job.cwd,
env,
detached: true,
shell: false,
stdio: "ignore",
windowsHide: true,
});
notifier.unref();
return await recordNotificationRelaySpawn(job.id, notifier.pid ?? null, env);
} catch (error) {
return await updateJob(job.id, (current) => {
if (!["pending", "delivering"].includes(current.notification?.status)) return current;
return {
...current,
notification: {
...(current.notification ?? {}),
status: "failed",
failedAt: nowIso(),
errorMessage: error instanceof Error ? error.message : String(error),
},
};
}, env);
}
}
export async function recordNotificationRelaySpawn(jobId, relayPid, env = process.env) {
return await updateJob(jobId, (current) => {
if (!["pending", "delivering"].includes(current.notification?.status)) return current;
return {
...current,
notification: {
...(current.notification ?? {}),
relayPid,
relayStartedAt: nowIso(),
},
};
}, env);
}
export async function runWorker(jobId, env = process.env) {
const initial = readJob(jobId, env);
if (initial.status !== "queued") return initial;
let workerIdentity = null;
try {
workerIdentity = getProcessIdentity(process.pid);
} catch {}
const starting = await updateJob(
jobId,
(current) => {
if (current.status !== "queued") return current;
return {
...current,
status: "starting",
phase: "starting",
startedAt: current.startedAt || nowIso(),
workerPid: process.pid,
workerIdentity,
};
},
env
);
if (starting.status !== "starting") return starting;
const logs = resolveJobLogs(jobId, env);
const limit = resolveMaxLogBytes(env);
const stdout = createBoundedLogWriter(logs.stdout, limit);
const stderr = createBoundedLogWriter(logs.stderr, limit);
try {
const child = spawnJobProcess(starting, env);
child.stdout?.on("data", (chunk) => stdout.append(chunk));
child.stderr?.on("data", (chunk) => stderr.append(chunk));
let pidIdentity = null;
if (Number.isInteger(child.pid)) {
try {
pidIdentity = getProcessIdentity(child.pid);
} catch {}
}
const running = await updateJob(
jobId,
(current) => {
if (current.status === "cancelled" || current.status === "cancelling") return current;
if (current.status !== "starting") return current;
return {
...current,
status: "running",
phase: "running",
pid: child.pid ?? null,
pidIdentity,
};
},
env
);
if (running.status === "cancelled" || running.status === "cancelling") {
try {
if (child.pid) process.kill(-child.pid, "SIGTERM");
} catch {}
}
const { code, signal } = await waitForClose(child);
const completedAt = nowIso();
const terminal = await updateJob(
jobId,
(current) => {
if (current.status === "cancelled" || current.status === "cancel_failed") return current;
if (current.status === "cancelling") {
return {
...current,
status: "cancelled",
phase: "cancelled",
completedAt,
exitCode: code,
signal,
lastPid: child.pid ?? current.pid ?? null,
pid: null,
pidIdentity: null,
};
}
if (TERMINAL_STATUSES.has(current.status)) return current;
const succeeded = code === 0 && signal == null;
return {
...current,
status: succeeded ? "completed" : "failed",
phase: succeeded ? "done" : "failed",
completedAt,
exitCode: code,
signal,
errorMessage: succeeded
? null
: signal
? `Process terminated by ${signal}.`
: `Process exited with code ${code}.`,
lastPid: child.pid ?? current.pid ?? null,
pid: null,
pidIdentity: null,
};
},
env
);
launchUserNotification(terminal, env);
await launchNotificationRelay(terminal, env);
return terminal;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
stderr.append(`\n[codex-process-jobs] ${message}\n`);
const terminal = await updateJob(
jobId,
(current) => {
if (TERMINAL_STATUSES.has(current.status)) return current;
return {
...current,
status: "failed",
phase: "failed",
completedAt: nowIso(),
errorMessage: message,
pid: null,
pidIdentity: null,
};
},
env
);
launchUserNotification(terminal, env);
await launchNotificationRelay(terminal, env);
return terminal;
} finally {
stdout.close();
stderr.close();
}
}
async function main() {
await runWorker(parseJobId(process.argv.slice(2)));
}
if (isCliEntry(import.meta.url)) {
main().catch((error) => {
process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
process.exitCode = 1;
});
}
SHA-256: 37fb9c5a154ae01104cea70d27afea98db6533e480197142d813daeb301a630c