Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions plugins/codex/agents/codex-rescue.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Selection guidance:
Forwarding rules:

- Use exactly one `Bash` call to invoke `node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" task ...`.
- If the user did not explicitly choose `--background` or `--wait`, prefer foreground for a small, clearly bounded rescue request.
- If the user did not explicitly choose `--background` or `--wait` and the task looks complicated, open-ended, multi-step, or likely to keep Codex running for a long time, prefer background execution.
- Default to `--auto-poll` on every `task` call. This runs the job in the background with a 5-minute polling cap so short rescues return inline and long ones surface a "still running" handoff (with the job id) instead of blocking the parent session indefinitely.
- If the user explicitly passes `--wait`, drop `--auto-poll` and run the foreground path (no cap). Use this when the user wants the call to block no matter how long Codex takes.
- If the user explicitly passes `--background`, drop `--auto-poll` and pass `--background` through. Use this when the user wants fire-and-forget enqueue and will follow up via `/codex:status` themselves.
- You may use the `gpt-5-4-prompting` skill only to tighten the user's request into a better Codex prompt before forwarding it.
- Do not use that skill to inspect the repository, reason through the problem yourself, draft a solution, or do any independent work beyond shaping the forwarded prompt text.
- Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own.
Expand Down
52 changes: 50 additions & 2 deletions plugins/codex/scripts/codex-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const ROOT_DIR = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
const REVIEW_SCHEMA = path.join(ROOT_DIR, "schemas", "review-output.schema.json");
const DEFAULT_STATUS_WAIT_TIMEOUT_MS = 240000;
const DEFAULT_STATUS_POLL_INTERVAL_MS = 2000;
const DEFAULT_TASK_AUTO_POLL_TIMEOUT_MS = 300000;
const VALID_REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
const MODEL_ALIASES = new Map([["spark", "gpt-5.3-codex-spark"]]);
const STOP_REVIEW_TASK_MARKER = "Run a stop-gate review of the previous Claude turn.";
Expand Down Expand Up @@ -703,8 +704,8 @@ async function handleReview(argv) {

async function handleTask(argv) {
const { options, positionals } = parseCommandInput(argv, {
valueOptions: ["model", "effort", "cwd", "prompt-file"],
booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"],
valueOptions: ["model", "effort", "cwd", "prompt-file", "auto-poll-timeout-ms"],
booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background", "auto-poll"],
aliasMap: {
m: "model"
}
Expand Down Expand Up @@ -746,6 +747,53 @@ async function handleTask(argv) {
return;
}

if (options["auto-poll"]) {
ensureCodexReady(cwd);
requireTaskRequest(prompt, resumeLast);

const job = buildTaskJob(workspaceRoot, taskMetadata, write);
const request = buildTaskRequest({
cwd,
model,
effort,
prompt,
write,
resumeLast,
jobId: job.id
});
const { payload: queuedPayload } = enqueueBackgroundTask(cwd, job, request);

const timeoutMs = Math.max(
0,
Number(options["auto-poll-timeout-ms"]) || DEFAULT_TASK_AUTO_POLL_TIMEOUT_MS
);
const startedAt = Date.now();
const snapshot = await waitForSingleJobSnapshot(cwd, job.id, { timeoutMs });
const elapsedSec = Math.round((Date.now() - startedAt) / 1000);

if (snapshot.waitTimedOut) {
const stillRunning = `${queuedPayload.title} still running after ${elapsedSec}s — job ${job.id}. Check /codex:status ${job.id} or /codex:result ${job.id} once it finishes.\n`;
outputCommandResult(
{ ...queuedPayload, waitTimedOut: true, elapsedSec, timeoutMs },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve running status in auto-poll timeout payload

When --auto-poll times out, the JSON response is built from queuedPayload, which hard-codes status: "queued"; this drops the real state from snapshot and reports queued even after the job has moved to running. Any caller that relies on --json to decide follow-up behavior can make the wrong decision (for example, treating an already-running task as never started). Use snapshot.job.status (and related fields) in the timeout payload instead of the original enqueue payload.

Useful? React with 👍 / 👎.

renderQueuedTaskLaunch(queuedPayload) + stillRunning,
options.json
);
return;
}

const { workspaceRoot: resolvedWorkspaceRoot, job: resolvedJob } = resolveResultJob(cwd, job.id);
const storedJob = readStoredJob(resolvedWorkspaceRoot, resolvedJob.id);
outputCommandResult(
{ job: resolvedJob, storedJob },
renderStoredJobResult(resolvedJob, storedJob),
options.json
);
if (resolvedJob.status === "failed" || resolvedJob.exitStatus === 1) {
process.exitCode = 1;
}
return;
}

const job = buildTaskJob(workspaceRoot, taskMetadata, write);
await runForegroundCommand(
job,
Expand Down
5 changes: 4 additions & 1 deletion plugins/codex/skills/codex-cli-runtime/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ Execution rules:

Command selection:
- Use exactly one `task` invocation per rescue handoff.
- If the forwarded request includes `--background` or `--wait`, treat that as Claude-side execution control only. Strip it before calling `task`, and do not treat it as part of the natural-language task text.
- Default execution mode: append `--auto-poll` to every `task` call. This enqueues the job in the background and polls for completion with a 5-minute cap so short rescues stay synchronous and long ones return a "still running" handoff instead of hanging the parent session.
- If the forwarded request includes `--wait`, treat that as opt-out from auto-poll. Strip `--wait` from the task text and DO NOT add `--auto-poll` — let `task` run foreground with no client-side cap.
- If the forwarded request includes `--background`, treat that as fire-and-forget. Strip it from the task text, pass `--background` through to `task`, and DO NOT add `--auto-poll`.
- `--background` and `--wait` are mutually exclusive with `--auto-poll`. Never combine them.
- If the forwarded request includes `--model`, normalize `spark` to `gpt-5.3-codex-spark` and pass it through to `task`.
- If the forwarded request includes `--effort`, pass it through to `task`.
- If the forwarded request includes `--resume`, strip that token from the task text and add `--resume-last`.
Expand Down