You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Claude Code Codex plugin (codex@openai-codex v1.0.5) fails every fresh delegated task with:
no rollout found for thread id <uuid>
Root cause is an ordering bug against current app-server semantics: the plugin calls thread/name/set immediately after thread/start, before the first turn runs. In codex-cli 0.142.5 a non-ephemeral thread's rollout file is created lazily at the first turn/start, not at thread/start. thread/name/set is a disk/thread-store path that requires the rollout to already exist, so it returns -32600 no rollout found for thread id …. The plugin's startThread() catch only swallows unknown variant / unknown method, so it rethrows this error and the whole task fails.
Because persistThread: true (which sets a thread name) is used for every task run, this is deterministic — the plugin's task path is effectively unusable on this CLI version, forcing users to fall back to codex exec.
Claude Code Codex plugin 1.0.5 (codex@openai-codex, gitCommitSha 80c31f9)
Transport: codex app-server over newline-delimited JSON-RPC 2.0
Where it happens (plugin source)
scripts/lib/codex.mjs — startThread():
asyncfunctionstartThread(client,cwd,options={}){constresponse=awaitclient.request("thread/start",buildThreadParams(cwd,options));constthreadId=response.thread.id;if(options.threadName){try{awaitclient.request("thread/name/set",{ threadId,name: options.threadName});}catch(err){// Only suppresses "unknown variant/method" from older CLIs.constmsg=String(err?.message??err??"");if(!msg.includes("unknown variant")&&!msg.includes("unknown method")){throwerr;// <-- "no rollout found" is rethrown here → task fails}}}returnresponse;}
runAppServerTurn() starts a non-ephemeral thread for tasks (ephemeral: options.persistThread ? false : true) and always passes a threadName, then runs turn/startafterstartThread() returns.
executeTaskRun() (scripts/codex-companion.mjs) always calls with persistThread: true and a threadName, so thread/name/set is always attempted before any turn.
app-server side (matches source at tag rust-v0.142.5): a plain thread/start inserts the thread into the in-memory map but writes no rollout to $CODEX_HOME/sessions/; the rollout + state-db row are created when the first turn begins. thread/name/set resolves the rollout from disk (resolve_rollout_path → no rollout found when absent), unlike turn/start, which uses the in-memory handle (and would report thread not found on a miss, not no rollout found).
Minimal reproduction (no plugin needed)
// repro.mjs — run: node repro.mjs (from any dir, with codex 0.142.5 on PATH)import{spawn}from"node:child_process";importreadlinefrom"node:readline";constp=spawn("codex",["app-server"],{stdio: ["pipe","pipe","pipe"]});p.stdout.setEncoding("utf8");constpending=newMap();letid=0;readline.createInterface({input: p.stdout}).on("line",(l)=>{if(!l.trim())return;letm;try{m=JSON.parse(l);}catch{return;}if(m.id!==undefined&&(m.result!==undefined||m.error!==undefined)){constr=pending.get(m.id);if(r){pending.delete(m.id);m.error ? r.reject(m.error) : r.resolve(m.result);}}});constreq=(method,params)=>newPromise((res,rej)=>{consti=++id;pending.set(i,{resolve: res,reject: rej});p.stdin.write(JSON.stringify({id: i, method, params })+"\n");});constnotify=(method,params)=>p.stdin.write(JSON.stringify({ method, params })+"\n");awaitreq("initialize",{clientInfo: {name: "repro",version: "0",title: "repro"},capabilities: {}});notify("initialized",{});constst=awaitreq("thread/start",{cwd: process.cwd(),model: null,approvalPolicy: "never",sandbox: "read-only",serviceName: "claude_code_codex_plugin",ephemeral: false});consttid=st.thread.id;try{awaitreq("thread/name/set",{threadId: tid,name: "My Task"});// BEFORE any turn — same as the pluginconsole.log("name/set OK");}catch(e){console.log("name/set FAILED:",JSON.stringify(e));// -> {"code":-32600,"message":"no rollout found for thread id <tid>"}}p.kill("SIGTERM");
Observed: name/set FAILED: {"code":-32600,"message":"no rollout found for thread id …"} — 10/10 runs, and still fails even with a 2000 ms delay before name/set (the rollout is genuinely not written until the first turn, not merely a flush race). If you instead run turn/start first and only then thread/name/set, the no rollout found error disappears.
Expected vs actual
Expected: delegating a fresh task through the Claude Code plugin runs the turn; naming the thread is best-effort and never fails the task.
Actual: the task fails immediately with no rollout found for thread id … before the turn ever runs.
Suggested fix (plugin side)
Make thread naming non-fatal / correctly ordered. Any of:
Treat thread/name/set failures as best-effort — extend the startThread() catch to also swallow no rollout found (and the post-turn thread metadata unavailable before name update variant). Naming is cosmetic here: --resume-last resolves the thread from the plugin's own tracked job.threadId (resolveLatestTrackedTaskThread → findLatestResumableTaskJob), not the codex-side thread name.
Summary
The Claude Code Codex plugin (
codex@openai-codexv1.0.5) fails every fresh delegated task with:Root cause is an ordering bug against current app-server semantics: the plugin calls
thread/name/setimmediately afterthread/start, before the first turn runs. In codex-cli 0.142.5 a non-ephemeral thread's rollout file is created lazily at the firstturn/start, not atthread/start.thread/name/setis a disk/thread-store path that requires the rollout to already exist, so it returns-32600 no rollout found for thread id …. The plugin'sstartThread()catch only swallowsunknown variant/unknown method, so it rethrows this error and the whole task fails.Because
persistThread: true(which sets a thread name) is used for every task run, this is deterministic — the plugin'staskpath is effectively unusable on this CLI version, forcing users to fall back tocodex exec.Environment
codex@openai-codex, gitCommitSha80c31f9)codex app-serverover newline-delimited JSON-RPC 2.0Where it happens (plugin source)
scripts/lib/codex.mjs—startThread():runAppServerTurn()starts a non-ephemeral thread for tasks (ephemeral: options.persistThread ? false : true) and always passes athreadName, then runsturn/startafterstartThread()returns.executeTaskRun()(scripts/codex-companion.mjs) always calls withpersistThread: trueand athreadName, sothread/name/setis always attempted before any turn.app-server side (matches source at tag
rust-v0.142.5): a plainthread/startinserts the thread into the in-memory map but writes no rollout to$CODEX_HOME/sessions/; the rollout + state-db row are created when the first turn begins.thread/name/setresolves the rollout from disk (resolve_rollout_path→no rollout foundwhen absent), unliketurn/start, which uses the in-memory handle (and would reportthread not foundon a miss, notno rollout found).Minimal reproduction (no plugin needed)
Observed:
name/set FAILED: {"code":-32600,"message":"no rollout found for thread id …"}— 10/10 runs, and still fails even with a 2000 ms delay beforename/set(the rollout is genuinely not written until the first turn, not merely a flush race). If you instead runturn/startfirst and only thenthread/name/set, theno rollout founderror disappears.Expected vs actual
no rollout found for thread id …before the turn ever runs.Suggested fix (plugin side)
Make thread naming non-fatal / correctly ordered. Any of:
thread/name/setfailures as best-effort — extend thestartThread()catch to also swallowno rollout found(and the post-turnthread metadata unavailable before name updatevariant). Naming is cosmetic here:--resume-lastresolves the thread from the plugin's own trackedjob.threadId(resolveLatestTrackedTaskThread→findLatestResumableTaskJob), not the codex-side thread name.thread/name/setafter the first turn has produced a rollout (aligns with the desired behavior in Auto-generate and apply a concise thread name after the first prompt #24289).Option 1 is the smallest and most robust across CLI versions.
Related
-32600is overloaded across "no rollout found" and "failed to load configuration" — please make the two discriminable #28496 —-32600overloaded acrossno rollout foundand config-load errors (same error surface; recommends fall-back-to-thread/startfor the resume half).thread/start.