{"record":{"id":"feafbdc6a7dd0e4e","repo":"musistudio/claude-code-router","slug":"no-active-turn-for-thread-params-threadid","errorCode":null,"errorMessage":"No active turn for thread  + params.threadId","messagePattern":"No active turn for thread  \\+ params\\.threadId","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/core/src/agents/codex/cli-middleware-runtime.ts","lineNumber":2762,"sourceCode":"        const key = activeKey(params.threadId, params.turnId);\n        const entry = this.active.get(key) || findActiveForThread(this.active, params.threadId);\n        if (entry) {\n          entry.child.kill(\"SIGTERM\");\n          this.active.delete(entry.key);\n          const thread = this.threads.get(entry.threadId);\n          const turn = thread && thread.turns.find((item) => item.id === entry.turnId);\n          if (turn) {\n            turn.status = \"interrupted\";\n            turn.completedAt = nowSeconds();\n            turn.durationMs = Math.max(0, (turn.completedAt - turn.startedAt) * 1000);\n          }\n        }\n        writeResponse(id, {});\n        return undefined;\n      }\n      case \"turn/steer\": {\n        const entry = findActiveForThread(this.active, params.threadId);\n        if (!entry || !entry.child.stdin) throw new Error(\"No active turn for thread \" + params.threadId);\n        entry.child.stdin.write(JSON.stringify(claudeInputMessage(params.input || params.message || params)) + \"\\n\");\n        writeResponse(id, {});\n        return undefined;\n      }\n      case \"model/list\":\n        writeResponse(id, modelList(params));\n        return undefined;\n      case \"modelProvider/capabilities/read\":\n        writeResponse(id, { namespaceTools: false, imageGeneration: false, webSearch: false });\n        return undefined;\n      case \"account/read\":\n        writeResponse(id, mockAccountRead());\n        return undefined;\n      case \"getAuthStatus\":\n        writeResponse(id, mockAuthStatus(Boolean(params.includeToken)));\n        return undefined;\n      case \"permissionProfile/list\":\n      case \"skills/list\":","sourceCodeStart":2744,"sourceCodeEnd":2780,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/cli-middleware-runtime.ts#L2744-L2780","documentation":"The turn/steer middleware handler tries to forward user input into the stdin of the currently running turn for a thread. It looks up an active child process by threadId and throws when no active turn (or no live stdin stream) exists for that thread. Steering only works while a turn is mid-flight.","triggerScenarios":"Sending a turn/steer request for a threadId that has no entry in the active-turn map: the turn already completed, was cancelled/crashed, the child's stdin closed, or the threadId is wrong/stale from a reconnecting client.","commonSituations":"Client UI sends a follow-up message after the previous turn finished but before refreshing state; race between turn completion event and user hitting send; child process crashed silently; reused threadId from a previous runtime session.","solutions":["Check that a turn is actually active for the thread before steering (poll thread/turn status)","Start a new turn instead of steering once the previous turn completed","Handle the race client-side: retry as a new prompt if steering fails","If it happens consistently, inspect child process stderr — the turn may be dying immediately"],"exampleFix":"// before\nawait middleware.request(\"turn/steer\", { threadId, input }); // throws if no active turn\n\n// after\nconst active = await middleware.request(\"thread/activeTurn\", { threadId }).catch(() => null);\nif (active) {\n  await middleware.request(\"turn/steer\", { threadId, input });\n} else {\n  await middleware.request(\"turn/start\", { threadId, input }); // start a fresh turn\n}","handlingStrategy":"fallback","validationCode":"const active = findActiveForThread(activeTurns, threadId);\nif (!active) { /* start a new turn instead of steering */ }","typeGuard":"function hasActiveTurn(active, threadId) { const e = active.get(String(threadId)); return Boolean(e && e.child && e.child.stdin && !e.child.stdin.destroyed); }","tryCatchPattern":"try { await steer(threadId, input); } catch (e) { if (/No active turn for thread/.test(String(e))) { await startTurn(threadId, input); } else throw e; }","preventionTips":["Confirm turn state before sending follow-up input","Refresh thread state after turn completion events","Treat steer failure as a signal to fall back to a new prompt"],"tags":["turn","steer","stdin","race-condition","thread"],"backgroundTag":"no-active-process-for-request","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}