mastra-ai/mastra · error · MastraError

AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED

AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED

Error message

Agent "${this.name}" ${method}() cannot resume tool call "${toolCallId}" because it is not suspended.

What it means

A MastraError (id AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED) thrown when resuming an agentic loop run with a `toolCallId` that is not currently in a suspended state in the run's snapshot. Resume targets specific suspended tool calls; the library verified the snapshot's suspended tools and did not find this toolCallId among them.

Source

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

    if (!isSuspended) {
      // A resume stream can expose the next suspension just before its snapshot is
      // persisted. Briefly poll after authorization so an immediate response to
      // that newly surfaced tool call is not rejected based on the prior snapshot.
      const effectiveMastra = this.#mastra ?? (await this.#getOrCreateEphemeralMastra());
      const workflowsStore = await effectiveMastra?.getStorage()?.getStore('workflows');
      const deadline = Date.now() + 2000;
      while (!isSuspended && workflowsStore && Date.now() < deadline) {
        await new Promise(resolve => setTimeout(resolve, 25));
        const latestSnapshot = await workflowsStore.loadWorkflowSnapshot({ workflowName: 'agentic-loop', runId });
        if (latestSnapshot && isTargetSuspended(latestSnapshot)) {
          resumeSnapshot = latestSnapshot;
          isSuspended = true;
        }
      }
    }

    if (!isSuspended) {
      throw new MastraError({
        id: 'AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED',
        domain: ErrorDomain.AGENT,
        category: ErrorCategory.USER,
        text: `Agent "${this.name}" ${method}() cannot resume tool call "${toolCallId}" because it is not suspended.`,
        details: {
          agentName: this.name,
          method,
          runId,
          toolCallId,
        },
      });
    }

    return resumeSnapshot;
  }

  /**
   * Suspend payloads persisted before they carried `toolCallId` only hold the

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use the exact toolCallId from the suspend event payload of the currently suspended run
  2. Check the run snapshot/status to list which tool calls are still suspended before resuming
  3. Guard against double-resume (disable the resume action after the first successful call)
  4. Ensure the id is the tool call id (not tool name or tool execution id)
  5. If the run already completed, start a new run instead of resuming

Example fix

// before
await agent.resume({ runId, toolCallId: 'humanApprovalTool' }); // tool name, not call id
// after
await agent.resume({ runId, toolCallId: suspendEvent.payload.toolCallId });
Defensive patterns

Strategy: validation

Validate before calling

function validateResumePayload(suspendEvent, runId, toolCallId) {
  if (suspendEvent.runId !== runId) throw new Error('runId mismatch');
  const suspended = suspendEvent.payload?.suspendedToolCallIds ?? [suspendEvent.payload?.toolCallId];
  if (!suspended.includes(toolCallId)) {
    throw new Error(`toolCallId ${toolCallId} is not suspended; suspended: ${suspended.join(', ')}`);
  }
}

Type guard

function isSuspendedToolCall(toolCallId, suspendedIds) {
  return typeof toolCallId === 'string' && suspendedIds.includes(toolCallId);
}

Try / catch

try {
  return await agent.resume({ runId, toolCallId, resumeData });
} catch (e) {
  if (e?.id === 'AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED') {
    logger.warn(`toolCallId ${toolCallId} already resumed or invalid; refresh run state`);
    return refreshRunState(runId);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `agent.resume({ runId, toolCallId, ... })` where the toolCallId belongs to a tool that already resumed/completed, a tool that never suspends, or a fabricated/mismatched id; also when the suspend point is in a different tool than the one addressed.

Common situations: UIs resuming with stale toolCallIds after a prior resume already succeeded; resuming multiple tool calls where one was already handled; persisting toolCallIds client-side and reusing them across retries; passing a tool *name* instead of the tool *call* id.

Related errors


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