mastra-ai/mastra · error · HTTPException

Run id is required

Error message

Run id is required

What it means

The tool-approval/approval-result style endpoint operates on a specific run and tool call, so params.runId must be present. The handler throws HTTP 400 'Run id is required' before even checking toolCallId. The runId ties the response to the suspended run awaiting approval.

Source

Thrown at packages/server/src/server/handlers/agents.ts:2529

  responseType: 'stream' as const,
  streamFormat: 'sse' as const,
  pathParamSchema: agentIdPathParams,
  bodySchema: approveToolCallBodySchema,
  responseSchema: toolCallResponseSchema,
  summary: 'Approve tool call',
  description: 'Approves a pending tool call and continues agent execution',
  tags: ['Agents', 'Tools'],
  requiresAuth: true,
  handler: async ({ mastra, agentId, abortSignal, requestContext, ...params }) => {
    try {
      const agent = await getAgentFromSystem({
        mastra,
        agentId,
        versionOptions: extractVersionOptions(requestContext),
      });

      if (!params.runId) {
        throw new HTTPException(400, { message: 'Run id is required' });
      }

      if (!params.toolCallId) {
        throw new HTTPException(400, { message: 'Tool call id is required' });
      }

      // UI Frameworks may send "client tools" in the body,
      // but it interferes with llm providers tool handling, so we remove them
      sanitizeBody(params, ['tools', 'actor']);

      await validateDurableToolCallAccess({
        mastra,
        agent,
        runId: params.runId,
        toolCallId: params.toolCallId,
        requestContext,
      });

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Include runId in the request body (taken from the suspended-run/watch event)
  2. Persist the runId alongside toolCallId when the run suspends so it survives restarts
  3. Check field naming matches the API schema (runId, camelCase)

Example fix

// before
await client.getAgent('a').approveToolCall({ toolCallId: 'call_1' });
// after
await client.getAgent('a').approveToolCall({ runId: 'run_abc', toolCallId: 'call_1' });
Defensive patterns

Strategy: validation

Validate before calling

function assertApprovalPayload(p: { runId?: string; toolCallId?: string }) {
  if (!p.runId) throw new Error('Run id is required');
  if (!p.toolCallId) throw new Error('Tool call id is required');
}

Type guard

function hasApprovalIds(p: { runId?: string; toolCallId?: string }): p is { runId: string; toolCallId: string } {
  return typeof p.runId === 'string' && p.runId.length > 0 && typeof p.toolCallId === 'string' && p.toolCallId.length > 0;
}

Try / catch

try {
  await respondToToolCall(payload);
} catch (e) {
  if (e?.status === 400 && /Run id is required/.test(e.message)) {
    console.error('Suspended-run state lost runId; recover from the stored suspension record');
  }
  throw e;
}

Prevention

When it happens

Trigger: POSTing a tool call approval/continue response without runId in the request body/params.

Common situations: Client reconstructing the resume request from a webhook payload missing the run ID; runId stored under a different key in the client's suspended-run state; restarting the client process and losing the active run reference.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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