mastra-ai/mastra · error · MastraError

AGENT_SEND_TOOL_APPROVAL_NO_ACTIVE_THREAD_RUN

AGENT_SEND_TOOL_APPROVAL_NO_ACTIVE_THREAD_RUN

Error message

Agent "${this.name}" sendToolApproval() could not find an active or suspended run for thread "${threadId}". The run may have already completed or been resumed.

What it means

Agent.sendToolApproval() first tries to resolve a runId from options; if absent, it looks up an active or suspended run for the thread (resolvedFromStorage). When neither path yields a runId, there is nothing to approve against, so it throws. The run may have finished, been resumed, or never existed for that thread.

Source

Thrown at packages/core/src/agent/agent.ts:9483

          category: ErrorCategory.USER,
          text:
            `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.`,
          details: {
            threadId,
            resourceId,
            agentName: this.name,
            runIds: matchingRuns.map(run => run.runId).join(', '),
          },
        });
      }

      runId = matchingRuns[0]?.runId;
      resolvedFromStorage = runId !== undefined;
    }

    if (!runId) {
      throw new MastraError({
        id: 'AGENT_SEND_TOOL_APPROVAL_NO_ACTIVE_THREAD_RUN',
        domain: ErrorDomain.AGENT,
        category: ErrorCategory.USER,
        text:
          `Agent "${this.name}" sendToolApproval() could not find an active or suspended run for thread "${threadId}". ` +
          `The run may have already completed or been resumed.`,
        details: {
          threadId,
          resourceId,
          agentName: this.name,
        },
      });
    }

    const resumeOptions = deepMerge(
      (streamOptions ?? {}) as Record<string, unknown>,
      executionOptions as Record<string, unknown>,
    ) as unknown as AgentExecutionOptions<OUTPUT>;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-check the thread's state: confirm there is a pending tool approval before calling sendToolApproval.
  2. If the run already completed, restart the conversation/turn instead of approving.
  3. Make the approval flow idempotent: track which approvals were already sent and skip duplicates.
  4. Verify threadId/resourceId correctness so the lookup targets the right thread.

Example fix

// before
await agent.sendToolApproval({ threadId }); // assumes a pending run exists
// after
const suspended = await agent.getSuspendedRuns?.({ threadId }); // or check storage
if (suspended?.length) {
  await agent.sendToolApproval({ threadId, toolCallId: suspended[0].toolCalls[0].toolCallId });
}
Defensive patterns

Strategy: validation

Validate before calling

const hasPending = await threadHasPendingToolApproval(threadId); // check suspended runs first
if (!hasPending) return; // nothing to approve; skip silently

Try / catch

try {
  await agent.sendToolApproval({ threadId, toolCallId });
} catch (e) {
  if (String(e?.id) === 'AGENT_SEND_TOOL_APPROVAL_NO_ACTIVE_THREAD_RUN') {
    // already completed/resumed: mark approval as stale and continue
    markApprovalResolved(threadId, toolCallId);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling agent.sendToolApproval({ threadId }) with no runId/toolCallId when the thread has zero active or suspended runs — e.g. the run already completed, was already approved/resumed, or the threadId is wrong.

Common situations: Approving a tool call after the stream already finished; double-submitting an approval (first call resumed the run); approving on a thread belonging to another resource/agent; approving after a deployment restart where in-memory active runs were lost and storage holds no suspended run.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/e8a2e912e8d63897. Report an issue: GitHub.