{"record":{"id":"5b11ad84a68a3776","repo":"mastra-ai/mastra","slug":"agent-send-tool-approval-ambiguous-suspended-runs","errorCode":"AGENT_SEND_TOOL_APPROVAL_AMBIGUOUS_SUSPENDED_RUNS","errorMessage":"Agent \"${this.name}\" sendToolApproval() found ${matchingRuns.length} suspended runs for thread \"${threadId}\". Pass a toolCallId to disambiguate, or resume a specific run with approveToolCall()/declineToolCall() and an explicit runId.","messagePattern":"Agent \"(.+?)\" sendToolApproval\\(\\) found (.+?) suspended runs for thread \"(.+?)\"\\. Pass a toolCallId to disambiguate, or resume a specific run with approveToolCall\\(\\)/declineToolCall\\(\\) and an explicit runId\\.","errorType":"error_code","errorClass":"MastraError","httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/agent.ts","lineNumber":9462,"sourceCode":"      // suspended-run discovery so approvals stay durable.\n      let suspendedRuns: AgentRun[] = [];\n      try {\n        ({ runs: suspendedRuns } = await this.listSuspendedRuns({ threadId, resourceId }));\n      } catch (error) {\n        // Only swallow the expected no-storage case — storage outages and\n        // store-driver errors must surface instead of masquerading as\n        // \"no suspended run exists\".\n        if (!(error instanceof MastraError) || error.id !== 'AGENT_LIST_SUSPENDED_RUNS_NO_STORAGE') {\n          throw error;\n        }\n      }\n\n      const matchingRuns = options.toolCallId\n        ? suspendedRuns.filter(run => run.toolCalls.some(toolCall => toolCall.toolCallId === options.toolCallId))\n        : suspendedRuns;\n\n      if (matchingRuns.length > 1) {\n        throw new MastraError({\n          id: 'AGENT_SEND_TOOL_APPROVAL_AMBIGUOUS_SUSPENDED_RUNS',\n          domain: ErrorDomain.AGENT,\n          category: ErrorCategory.USER,\n          text:\n            `Agent \"${this.name}\" sendToolApproval() found ${matchingRuns.length} suspended runs for thread \"${threadId}\". ` +\n            `Pass a toolCallId to disambiguate, or resume a specific run with approveToolCall()/declineToolCall() and an explicit runId.`,\n          details: {\n            threadId,\n            resourceId,\n            agentName: this.name,\n            runIds: matchingRuns.map(run => run.runId).join(', '),\n          },\n        });\n      }\n\n      runId = matchingRuns[0]?.runId;\n      resolvedFromStorage = runId !== undefined;\n    }","sourceCodeStart":9444,"sourceCodeEnd":9480,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent/agent.ts#L9444-L9480","documentation":"Agent.sendToolApproval() collects all suspended runs for the thread, optionally filtered by toolCallId. If more than one run still matches, the target of the approval is ambiguous, so the library refuses to guess and throws. The error explicitly tells you how to disambiguate: pass a toolCallId, or use approveToolCall()/declineToolCall() with an explicit runId.","triggerScenarios":"Calling agent.sendToolApproval({ threadId }) without toolCallId while the thread has multiple suspended runs; or passing a toolCallId that matches tool calls in more than one suspended run (duplicate toolCallIds across runs).","commonSituations":"Threads with several concurrently suspended agent runs (parallel tool calls or multiple resumed streams); callers storing only threadId and omitting toolCallId; tool call IDs reused after retries; bulk approval UI iterating a thread with several pending runs.","solutions":["Pass the toolCallId of the specific tool call you are approving in options.toolCallId.","Switch to agent.approveToolCall() or agent.declineToolCall() with an explicit runId to target one run.","List the suspended runs for the thread and confirm which run/tool call you intend, then retry with the explicit identifiers."],"exampleFix":"// before\nawait agent.sendToolApproval({ threadId });\n// after\nawait agent.sendToolApproval({ threadId, toolCallId: 'call_abc123' });\n// or\nawait agent.approveToolCall({ threadId, runId: 'run_456', toolCallId: 'call_abc123' });","handlingStrategy":"validation","validationCode":"const suspended = await getSuspendedRunsForThread(threadId); // your storage lookup\nif (suspended.length > 1 && !options.toolCallId) {\n  throw new Error('Multiple suspended runs; supply toolCallId or explicit runId.');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await agent.sendToolApproval({ threadId, toolCallId });\n} catch (e) {\n  if (String(e?.id) === 'AGENT_SEND_TOOL_APPROVAL_AMBIGUOUS_SUSPENDED_RUNS') {\n    // surface a picker to the user listing the suspended runs/tool calls\n    showDisambiguationPrompt(e);\n  } else throw e;\n}","preventionTips":["Always include toolCallId in sendToolApproval calls.","In multi-run threads, prefer approveToolCall()/declineToolCall() with an explicit runId.","Model one pending tool approval per thread turn where possible to avoid ambiguity."],"tags":["agent","tool-approval","ambiguity","user-input","suspended-run"],"backgroundTag":"ambiguous-suspended-run","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}