mastra-ai/mastra · error

tool_result must be preceded by a tool_call

Error message

tool_result must be preceded by a tool_call

What it means

In agent.ts, onToolResultPart throws 'tool_result must be preceded by a tool_call' when a stream chunk carries a tool result but message.toolInvocations is null, meaning no earlier tool_call chunk created an invocation entry. The stream protocol requires each tool_result to correspond to a tracked tool invocation so the result can replace the pending call by toolCallId.

Source

Thrown at client-sdks/client-js/src/resources/agent.ts:1577

              step,
              ...value,
              result,
            } as const;

            // store the result in the tool invocation
            message.toolInvocations![message.toolInvocations!.length - 1] = invocation;

            updateToolInvocationPart(value.toolCallId, invocation, (value as MaybeProviderMetadata).providerMetadata);

            execUpdate();
          }
        }
      },
      onToolResultPart(value) {
        const toolInvocations = message.toolInvocations;

        if (toolInvocations == null) {
          throw new Error('tool_result must be preceded by a tool_call');
        }

        // find if there is any tool invocation with the same toolCallId
        // and replace it with the result
        const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);

        if (toolInvocationIndex === -1) {
          throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
        }

        const invocation = {
          ...toolInvocations[toolInvocationIndex],
          state: 'result' as const,
          ...value,
        } as const;

        toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure you consume the stream from the beginning (or from a checkpoint that includes the tool_call chunk).
  2. Verify server/middleware isn't dropping or reordering tool_call parts.
  3. Initialize message.toolInvocations to [] on the message object before processing chunks if you process partial streams defensively.
  4. Check SDK/server version compatibility for the stream protocol's tool part shapes.
  5. Guard with a null check and skip/log orphan tool results instead of throwing, if out-of-order data is acceptable in your app.

Example fix

// before
onToolResultPart(value) {
  const toolInvocations = message.toolInvocations;
  if (toolInvocations == null) throw new Error('tool_result must be preceded by a tool_call');
// after (app-side guard when processing partial streams)
if (!message.toolInvocations) { console.warn('Skipping orphan tool_result', value.toolCallId); return; }
const idx = message.toolInvocations.findIndex(i => i.toolCallId === value.toolCallId);
Defensive patterns

Strategy: validation

Validate before calling

// before processing a partial/historical stream, ensure invocation state exists
if (!message.toolInvocations) message.toolInvocations = [];
const knownCallIds = new Set(message.toolInvocations.map(i => i.toolCallId));
if ('toolCallId' in incomingResult && !knownCallIds.has(incomingResult.toolCallId)) {
  console.warn('tool_result without matching tool_call — stream may be partial/out of order');
}

Type guard

function hasToolInvocations(m: unknown): m is { toolInvocations: Array<{ toolCallId: string }> } {
  return !!m && typeof m === 'object' && Array.isArray((m as any).toolInvocations);
}

Try / catch

try {
  await consumeAgentStream(stream);
} catch (err) {
  if ((err as Error).message === 'tool_result must be preceded by a tool_call') {
    console.error('Stream was joined late or tool_call chunk was dropped — restart consumption from the beginning');
  }
  throw err;
}

Prevention

When it happens

Trigger: Consuming a partially persisted or truncated message stream where the tool_call chunk was missed (joined mid-stream, resumed from an offset, or the server emitted results out of order); feeding historical/loaded messages through the processor without their preceding tool_call chunks.

Common situations: Reconnecting to a thread stream after the tool call already streamed; custom servers or middleware dropping tool_call chunks; loading stored threads whose serialization omitted toolInvocations; version mismatches where the client expects toolInvocations but the server emits a different shape.

Related errors


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