musistudio/claude-code-router · error · Error

thread not found: + id

Error message

thread not found:  + id

What it means

requireThread() looks up a thread by stringified id in the runtime's in-memory threads Map and throws if absent. It is the guard used by all thread-scoped operations, so any request referencing an unknown, evicted, or not-yet-created thread id fails here.

Source

Thrown at packages/core/src/agents/codex/cli-middleware-runtime.ts:3598

  getOrCreateThread(params) {
    const requested = params.threadId || params.thread_id;
    if (requested && this.threads.has(requested)) return this.threads.get(requested);
    if (requested) {
      const thread = this.createThread({ ...params, cwd: params.cwd || process.cwd() });
      thread.id = requested;
      thread.sessionId = requested;
      thread.claudeSessionId = requested;
      this.threads.delete(Array.from(this.threads.keys()).find((key) => this.threads.get(key) === thread));
      this.threads.set(requested, thread);
      return thread;
    }
    return this.createThread(params);
  }

  requireThread(threadId) {
    const id = String(threadId || "");
    const thread = this.threads.get(id);
    if (!thread) throw new Error("thread not found: " + id);
    return thread;
  }

  threadList(params) {
    let data = Array.from(this.threads.values())
      .filter((thread) => Boolean(thread.archived) === Boolean(params.archived))
      .map((thread) => threadJson(thread, false));
    const search = String(params.search || params.query || "").toLowerCase().trim();
    if (search) {
      data = data.filter((thread) => JSON.stringify(thread).toLowerCase().includes(search));
    }
    data.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
    if (Number.isFinite(params.limit)) data = data.slice(0, params.limit);
    return { data, nextCursor: null, backwardsCursor: null };
  }

  startTurn(params) {
    const thread = this.requireThread(params.threadId);

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Create the thread first via createThread and use the returned id
  2. If the runtime restarted, re-create threads or rehydrate state instead of reusing old ids
  3. Validate the id is non-empty and matches an entry from threadList before use
  4. Guard calls with a threads.has() check (or catch and recreate)

Example fix

// before
const thread = requireThread(threadId); // throws 'thread not found'

// after
function getOrCreateThread(runtime, threadId) {
  return runtime.threads?.get(String(threadId || "")) || runtime.createThread({ id: threadId });
}
Defensive patterns

Strategy: type-guard

Validate before calling

const id = String(threadId || ""); if (!runtime.threads.has(id)) { thread = await runtime.createThread(params); }

Type guard

function threadExists(runtime, threadId) { return runtime.threads.has(String(threadId || "")); }

Try / catch

try { return requireThread(id); } catch (e) { if (/thread not found/.test(String(e))) return createThread({ id }); throw e; }

Prevention

When it happens

Trigger: Calling thread-scoped methods (messages, send, archive, etc.) with a threadId that was never created via createThread, belongs to a previous runtime process, was archived/removed, or a falsy/empty id stringified to ''.

Common situations: Persisted threadId reused after middleware restart (threads are in-memory); typo or truncated id; race where the client sends messages before createThread resolves; thread pruned by list filters.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/98153dc5791ca23f. Report an issue: GitHub.