musistudio/claude-code-router · warning · Error

Another Agent request is already waiting for this conversati

Error message

Another Agent request is already waiting for this conversation.

What it means

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.

Source

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

      const response = method === "item/permissions/requestApproval"
        ? claudeControlPermissionResponse(message, requestId, approval)
        : claudeControlElicitationResponse(requestId, approval);
      child.stdin.write(JSON.stringify(response) + "\n");
    }).catch((error) => {
      child.stdin.write(JSON.stringify({ type: "control_response", response: { subtype: "error", request_id: requestId, error: formatError(error) } }) + "\n");
    });
  }

  async requestBotControl(work, requestId, method, params) {
    const context = work.botContext;
    const key = context.conversationKey;
    if (method === "item/permissions/requestApproval" && !context.bridge.config.shellEnabled && isShellPermissionRequest(params)) {
      return { decision: "deny", reason: "Agent shell tools are disabled in Bot Settings." };
    }
    if (method === "item/permissions/requestApproval" && this.botSessionApprovals.has(key)) {
      return { decision: "allow", scope: "session" };
    }
    if (this.botPendingApprovals.has(key)) throw new Error("Another Agent request is already waiting for this conversation.");
    const isPermission = method === "item/permissions/requestApproval";
    const label = isPermission ? String(params.reason || "Agent permission") : String(params.message || "Agent input request");
    const fallbackText = isPermission
      ? label + "\nReply /session approve, /session approve session, or /session deny."
      : label + "\nReply /session answer <text> or /session deny.";
    const actions = isPermission
      ? [
          { type: "button", label: "Approve once", value: "/session approve" },
          { type: "button", label: "Approve for session", value: "/session approve session" },
          { type: "button", label: "Deny", value: "/session deny" }
        ]
      : [{ type: "button", label: "Deny", value: "/session deny" }];
    const promise = new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        if (this.botPendingApprovals.get(key) && this.botPendingApprovals.get(key).requestId === requestId) {
          this.botPendingApprovals.delete(key);
        }
        reject(new Error("Bot approval timed out."));

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Answer or deny the pending request first (/session approve or /session deny), which clears the entry
  2. Design callers to queue, not parallelize, interactions per conversation
  3. If the entry is stale after a dropped response, restart the session/runtime to clear botPendingApprovals
  4. Add client-side dedupe on the conversation key before issuing a second request

Example fix

// before
await bridge.request("item/permissions/requestApproval", { ... }); // second call throws

// after
if (!bridge.botPendingApprovals.has(conversationKey)) {
  await bridge.request("item/permissions/requestApproval", { ... });
} else {
  queueUntilApprovalSettled(conversationKey, request); // serialize per conversation
}
Defensive patterns

Strategy: validation

Validate before calling

if (bridge.botPendingApprovals.has(conversationKey)) { /* queue or surface existing prompt instead of issuing another */ }

Type guard

null

Try / catch

try { await requestApproval(params); } catch (e) { if (/already waiting for this conversation/.test(String(e))) { await answerPendingApproval(key); } else throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.


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