mastra-ai/mastra · error · Error

ToolCall compounds must be rendered within ToolCall

Error message

ToolCall compounds must be rendered within ToolCall

What it means

ToolCall is a collapsible compound component whose subcomponents read ToolCallContext (status, open state) through useToolCall. The context defaults to null and is only provided by the root <ToolCall> (a Collapsible), so any compound child rendered outside it throws this error. Without the root, the child cannot know the call's status or open state.

Source

Thrown at packages/playground-ui/src/ds/components/ai/tool-call/tool-call.tsx:26

import { Shimmer } from '@/ds/components/Shimmer';
import { Txt } from '@/ds/components/Txt';
import { cn } from '@/lib/utils';

type ToolCallStatus = 'idle' | 'running' | 'error';

interface ToolCallContextValue {
  open: boolean;
  status: ToolCallStatus;
}

const ToolCallContext = createContext<ToolCallContextValue | undefined>(undefined);

// Stryker disable next-line ArrowFunction: the default callback is intentionally behaviorless.
const noopOpenChange = () => {};

function useToolCall() {
  const context = useContext(ToolCallContext);
  if (!context) throw new Error('ToolCall compounds must be rendered within ToolCall');
  return context;
}

export interface ToolCallProps extends Omit<
  ComponentProps<typeof Collapsible>,
  'defaultOpen' | 'onOpenChange' | 'open'
> {
  open?: boolean;
  defaultOpen?: boolean;
  onOpenChange?: (open: boolean) => void;
  status?: ToolCallStatus;
}

export function ToolCall({
  open: controlledOpen,
  defaultOpen = false,
  onOpenChange = noopOpenChange,
  status = 'idle',

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Render all ToolCall compound children within <ToolCall>.
  2. Re-add the <ToolCall> root wrapper if a refactor dropped it.
  3. If status/open must be shared across trees, lift that state up and pass it via props instead of using the compounds elsewhere.
  4. Check conditional rendering so root and children mount/unmount together.

Example fix

// before
<ToolCall.Header status={status} /> {/* throws: no root */}
<ToolCall args={args} />

// after
<ToolCall {...toolCallProps}>
  <ToolCall.Header />
</ToolCall>
Defensive patterns

Strategy: validation

Validate before calling

const ctx = useContext(ToolCallContext);
if (!ctx) {
  // not within <ToolCall>: render with explicit status/open props instead
}

Type guard

const hasToolCallContext = (v: ToolCallContextValue | null): v is ToolCallContextValue => v != null;

Try / catch

try {
  const { status, open } = useToolCall();
  // use status/open
} catch {
  // outside <ToolCall>: fall back to props
}

Prevention

When it happens

Trigger: Rendering ToolCall.Header/Body/Trigger-like compounds outside <ToolCall>; conditionally unmounting <ToolCall> while children persist; importing a subcomponent into another view without the root wrapper.

Common situations: Building a custom tool-call list where the header is separated from the root; tests or stories rendering subcomponents in isolation; refactoring the collapsible so the provider is removed but compound children remain.

Related errors


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