openai / openai/codex-plugin-cc
task: add `--resume-thread <id>` — jobs killed by a usage limit cannot be resumed from the plugin
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 33.3k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
Summary
task can only resume the latest thread (--resume-last), and a background job killed by a usage limit is left as failed with no way to continue it from the plugin — even though /codex:result prints the thread's Codex session ID and the runtime already supports resumeThreadId.
Scenario (plugin 1.0.6, codex-cli 0.151.0, Linux)
- Launch five
/codex:rescue --backgroundaudits in parallel (read-only,--effort high). - The account's 5-hour window runs out mid-run. All five jobs end with:
Each had already read a large part of the repo (the expensive part).[codex] Codex error: You've hit your usage limit. ... try again at 3:40 PM. [codex] Turn failed. - After the reset, there is no plugin way to continue them:
--resume-lastonly targets the newest thread, and/codex:resultjust printsResume in Codex: codex resume <id>— i.e. it hands the id back and sends the user to the terminal.
Workaround that works today: codex exec --sandbox read-only resume <id> "<continue prompt>" per thread.
Proposed fix (small)
Add --resume-thread <id> to task, mutually exclusive with --resume-last / --fresh, and thread it through buildTaskRequest → executeTaskRun so both the foreground and background paths honour it. Patch against 1.0.6 (companion script + the codex-cli-runtime skill so the rescue subagent knows the flag) is below — ~40 lines, no behaviour change for existing flags.
A natural follow-up: when a job fails on a usage-limit error (the message carries the reset time), keep the thread id in the job record and let /codex:status offer a one-liner to resume it after the reset, instead of marking it terminally failed.
Patch
--- a/scripts/codex-companion.mjs
+++ b/scripts/codex-companion.mjs
@@ -79,7 +79,7 @@
" node scripts/codex-companion.mjs setup [--enable-review-gate|--disable-review-gate] [--json]",
" node scripts/codex-companion.mjs review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>]",
" node scripts/codex-companion.mjs adversarial-review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>] [focus text]",
- " node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]",
+ " node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--resume-thread <id>|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]",
" node scripts/codex-companion.mjs transfer [--source <claude-jsonl>] [--json]",
" node scripts/codex-companion.mjs status [job-id] [--all] [--json]",
" node scripts/codex-companion.mjs result [job-id] [--json]",
@@ -464,11 +464,14 @@
const taskMetadata = buildTaskRunMetadata({
prompt: request.prompt,
- resumeLast: request.resumeLast
+ resumeLast: request.resumeLast || Boolean(request.resumeThread)
});
let resumeThreadId = null;
- if (request.resumeLast) {
+ if (request.resumeThread) {
+ // Explicit thread id (e.g. a job cut by a usage limit): skip the latest-thread lookup.
+ resumeThreadId = String(request.resumeThread).trim();
+ } else if (request.resumeLast) {
const latestThread = await resolveLatestTrackedTaskThread(workspaceRoot, {
excludeJobId: request.jobId
});
@@ -479,7 +482,7 @@
}
if (!request.prompt && !resumeThreadId) {
- throw new Error("Provide a prompt, a prompt file, piped stdin, or use --resume-last.");
+ throw new Error("Provide a prompt, a prompt file, piped stdin, or use --resume-last / --resume-thread <id>.");
}
const result = await runAppServerTurn(workspaceRoot, {
@@ -601,7 +604,7 @@
});
}
-function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, jobId }) {
+function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, resumeThread = null, jobId }) {
return {
cwd,
model,
@@ -609,6 +612,7 @@
prompt,
write,
resumeLast,
+ resumeThread,
jobId
};
}
@@ -649,9 +653,9 @@
return positionalPrompt || readStdinIfPiped();
}
-function requireTaskRequest(prompt, resumeLast) {
- if (!prompt && !resumeLast) {
- throw new Error("Provide a prompt, a prompt file, piped stdin, or use --resume-last.");
+function requireTaskRequest(prompt, resumeLast, resumeThread = null) {
+ if (!prompt && !resumeLast && !resumeThread) {
+ throw new Error("Provide a prompt, a prompt file, piped stdin, or use --resume-last / --resume-thread <id>.");
}
}
@@ -761,7 +765,7 @@
async function handleTask(argv) {
const { options, positionals } = parseCommandInput(argv, {
- valueOptions: ["model", "effort", "cwd", "prompt-file"],
+ valueOptions: ["model", "effort", "cwd", "prompt-file", "resume-thread"],
booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"],
aliasMap: {
m: "model"
@@ -775,19 +779,23 @@
const prompt = readTaskPrompt(cwd, options, positionals);
const resumeLast = Boolean(options["resume-last"] || options.resume);
+ const resumeThread = options["resume-thread"] ? String(options["resume-thread"]).trim() : null;
const fresh = Boolean(options.fresh);
- if (resumeLast && fresh) {
- throw new Error("Choose either --resume/--resume-last or --fresh.");
+ if ((resumeLast || resumeThread) && fresh) {
+ throw new Error("Choose either --resume/--resume-last/--resume-thread or --fresh.");
+ }
+ if (resumeLast && resumeThread) {
+ throw new Error("Choose either --resume-last or --resume-thread <id>, not both.");
}
const write = Boolean(options.write);
const taskMetadata = buildTaskRunMetadata({
prompt,
- resumeLast
+ resumeLast: resumeLast || Boolean(resumeThread)
});
if (options.background) {
ensureCodexAvailable(cwd);
- requireTaskRequest(prompt, resumeLast);
+ requireTaskRequest(prompt, resumeLast, resumeThread);
const job = buildTaskJob(workspaceRoot, taskMetadata, write);
const request = buildTaskRequest({
@@ -797,6 +805,7 @@
prompt,
write,
resumeLast,
+ resumeThread,
jobId: job.id
});
const { payload } = enqueueBackgroundTask(cwd, job, request);
@@ -815,6 +824,7 @@
prompt,
write,
resumeLast,
+ resumeThread,
jobId: job.id,
onProgress: progress
}),
--- a/plugins/codex/skills/codex-cli-runtime/SKILL.md
+++ b/plugins/codex/skills/codex-cli-runtime/SKILL.md
@@ -30,6 +30,7 @@
- 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`.
- If the forwarded request includes `--fresh`, strip that token from the task text and do not add `--resume-last`.
+- If the forwarded request includes `--resume-thread <id>` (a Codex session/thread UUID, e.g. from a `/codex:result` line "Codex session ID:" of a job that failed on a usage limit), strip both tokens from the task text and pass `--resume-thread <id>` to `task`. This resumes THAT thread with its context instead of the latest one.
- `--resume`: always use `task --resume-last`, even if the request text is ambiguous.
- `--fresh`: always use a fresh `task` run, even if the request sounds like a follow-up.
- `--effort`: accepted values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in scripts/codex-companion.mjs at handleTask, buildTaskRequest, and executeTaskRun, then review plugins/codex/skills/codex-cli-runtime/SKILL.md. Verify that --resume-thread accepts an explicit session ID, works in foreground and background runs, and is rejected with --resume-last or --fresh; update the runtime guidance accordingly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- cli, documentation
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100