Skip to content

Claude Code Codex plugin: fresh tasks fail with "no rollout found for thread id" — thread/name/set is called before the first turn #31158

Open

Description

@T2hhbmEK

Summary

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.

Environment

  • codex-cli 0.142.5 (Homebrew cask), macOS (Darwin arm64)
  • 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():

async function startThread(client, cwd, options = {}) {
  const response = await client.request("thread/start", buildThreadParams(cwd, options));
  const threadId = response.thread.id;
  if (options.threadName) {
    try {
      await client.request("thread/name/set", { threadId, name: options.threadName });
    } catch (err) {
      // Only suppresses "unknown variant/method" from older CLIs.
      const msg = String(err?.message ?? err ?? "");
      if (!msg.includes("unknown variant") && !msg.includes("unknown method")) {
        throw err;  // <-- "no rollout found" is rethrown here → task fails
      }
    }
  }
  return response;
}
  • runAppServerTurn() starts a non-ephemeral thread for tasks (ephemeral: options.persistThread ? false : true) and always passes a threadName, then runs turn/start after startThread() 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";
import readline from "node:readline";

const p = spawn("codex", ["app-server"], { stdio: ["pipe", "pipe", "pipe"] });
p.stdout.setEncoding("utf8");
const pending = new Map(); let id = 0;
readline.createInterface({ input: p.stdout }).on("line", (l) => {
  if (!l.trim()) return; let m; try { m = JSON.parse(l); } catch { return; }
  if (m.id !== undefined && (m.result !== undefined || m.error !== undefined)) {
    const r = pending.get(m.id); if (r) { pending.delete(m.id); m.error ? r.reject(m.error) : r.resolve(m.result); }
  }
});
const req = (method, params) => new Promise((res, rej) => { const i = ++id; pending.set(i, { resolve: res, reject: rej }); p.stdin.write(JSON.stringify({ id: i, method, params }) + "\n"); });
const notify = (method, params) => p.stdin.write(JSON.stringify({ method, params }) + "\n");

await req("initialize", { clientInfo: { name: "repro", version: "0", title: "repro" }, capabilities: {} });
notify("initialized", {});
const st = await req("thread/start", { cwd: process.cwd(), model: null, approvalPolicy: "never", sandbox: "read-only", serviceName: "claude_code_codex_plugin", ephemeral: false });
const tid = st.thread.id;
try {
  await req("thread/name/set", { threadId: tid, name: "My Task" }); // BEFORE any turn — same as the plugin
  console.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:

  1. 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.
  2. Reorder — apply thread/name/set after 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

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    app-serverIssues involving app server protocol or interfaces
    skillsIssues related to skills
    on Jul 5, 2026
  2. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. T2hhbmEK commented on Jul 5, 2026

    @T2hhbmEK
    Author

    Not a duplicate of #31004.

    #31004 is the Codex Desktop app on Windows becoming broadly unusable (new chats time out, "Loading…" never resolves) — and that reporter says the CLI works.

    This (#31158) is the Claude Code Codex plugin on codex app-server (macOS): a deterministic, network/account-independent ordering bug with a ~25-line repro — thread/name/set is sent right after thread/start, but a non-ephemeral thread's rollout is only created at the first turn/start, so the name-set disk read fails with no rollout found and the plugin rethrows it.

    Only the error string overlaps, and #28496 notes that string is overloaded across unrelated cases. Happy to link as related, not duplicate.

  4. lsdlsd88 commented on Nov 9, 2026

    @lsdlsd88

    still an issue??

    Error starting chat
    no rollout found for thread id 01a1bf79-0259-74e7-8725-ba176a7e7045

    any workaroud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingskillsIssues related to skills

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions