{"record":{"id":"05f86271ef15b4f6","repo":"musistudio/claude-code-router","slug":"another-agent-request-is-already-waiting-for-this","errorCode":null,"errorMessage":"Another Agent request is already waiting for this conversation.","messagePattern":"Another Agent request is already waiting for this conversation\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/core/src/agents/codex/cli-middleware-runtime.ts","lineNumber":3877,"sourceCode":"      const response = method === \"item/permissions/requestApproval\"\n        ? claudeControlPermissionResponse(message, requestId, approval)\n        : claudeControlElicitationResponse(requestId, approval);\n      child.stdin.write(JSON.stringify(response) + \"\\n\");\n    }).catch((error) => {\n      child.stdin.write(JSON.stringify({ type: \"control_response\", response: { subtype: \"error\", request_id: requestId, error: formatError(error) } }) + \"\\n\");\n    });\n  }\n\n  async requestBotControl(work, requestId, method, params) {\n    const context = work.botContext;\n    const key = context.conversationKey;\n    if (method === \"item/permissions/requestApproval\" && !context.bridge.config.shellEnabled && isShellPermissionRequest(params)) {\n      return { decision: \"deny\", reason: \"Agent shell tools are disabled in Bot Settings.\" };\n    }\n    if (method === \"item/permissions/requestApproval\" && this.botSessionApprovals.has(key)) {\n      return { decision: \"allow\", scope: \"session\" };\n    }\n    if (this.botPendingApprovals.has(key)) throw new Error(\"Another Agent request is already waiting for this conversation.\");\n    const isPermission = method === \"item/permissions/requestApproval\";\n    const label = isPermission ? String(params.reason || \"Agent permission\") : String(params.message || \"Agent input request\");\n    const fallbackText = isPermission\n      ? label + \"\\nReply /session approve, /session approve session, or /session deny.\"\n      : label + \"\\nReply /session answer <text> or /session deny.\";\n    const actions = isPermission\n      ? [\n          { type: \"button\", label: \"Approve once\", value: \"/session approve\" },\n          { type: \"button\", label: \"Approve for session\", value: \"/session approve session\" },\n          { type: \"button\", label: \"Deny\", value: \"/session deny\" }\n        ]\n      : [{ type: \"button\", label: \"Deny\", value: \"/session deny\" }];\n    const promise = new Promise((resolve, reject) => {\n      const timeout = setTimeout(() => {\n        if (this.botPendingApprovals.get(key) && this.botPendingApprovals.get(key).requestId === requestId) {\n          this.botPendingApprovals.delete(key);\n        }\n        reject(new Error(\"Bot approval timed out.\"));","sourceCodeStart":3859,"sourceCodeEnd":3895,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/cli-middleware-runtime.ts#L3859-L3895","documentation":"The middleware serializes outbound bot interaction requests per conversation: while one permission-approval or input request is pending, botPendingApprovals already contains the key and any second interaction for the same conversation throws immediately. This prevents competing approval prompts within one conversation.","triggerScenarios":"Issuing item/permissions/requestApproval or an input request for a conversation that already has an unanswered pending request (key = conversation), e.g. the agent requests two tool approvals in quick succession, or a user triggers a new action before answering the previous prompt.","commonSituations":"Parallel tool calls needing approval; user ignores the first prompt and sends another message; approval response lost (timeout/disconnect) so the entry is never cleared; UI bug that double-fires the request.","solutions":["Answer or deny the pending request first (/session approve or /session deny), which clears the entry","Design callers to queue, not parallelize, interactions per conversation","If the entry is stale after a dropped response, restart the session/runtime to clear botPendingApprovals","Add client-side dedupe on the conversation key before issuing a second request"],"exampleFix":"// before\nawait bridge.request(\"item/permissions/requestApproval\", { ... }); // second call throws\n\n// after\nif (!bridge.botPendingApprovals.has(conversationKey)) {\n  await bridge.request(\"item/permissions/requestApproval\", { ... });\n} else {\n  queueUntilApprovalSettled(conversationKey, request); // serialize per conversation\n}","handlingStrategy":"validation","validationCode":"if (bridge.botPendingApprovals.has(conversationKey)) { /* queue or surface existing prompt instead of issuing another */ }","typeGuard":"null","tryCatchPattern":"try { await requestApproval(params); } catch (e) { if (/already waiting for this conversation/.test(String(e))) { await answerPendingApproval(key); } else throw e; }","preventionTips":["Serialize approval/input requests per conversation","Always resolve pending prompts before triggering new agent actions","Clear stale pending entries on session restart or disconnect"],"tags":["approval","concurrency","conversation","pending-request"],"backgroundTag":"concurrent-request-conflict","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}