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
- Create the thread first via createThread and use the returned id
- If the runtime restarted, re-create threads or rehydrate state instead of reusing old ids
- Validate the id is non-empty and matches an entry from threadList before use
- 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
- Never reuse thread ids across runtime restarts — threads are in-memory
- Always use the id returned from createThread
- List threads before operating on one obtained from external state
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.