janhq/jan · error · Error

Tool components must be used within Tool

Error message

Tool components must be used within Tool

What it means

useTool reads ToolContext, which is only populated by the Tool component in tool.tsx. If the hook runs outside any Tool ancestor, useContext returns null and the hook throws. This guards against rendering Tool sub-components (header, content, input, toolbar) standalone.

Solutions

  1. Wrap the sub-components in a <Tool toolCallId={...} ...> collapsible before rendering them.
  2. Remove the stray useTool-consuming component if the Tool wrapper is no longer needed.
  3. In tests/stories, add a minimal Tool provider wrapper as the decorator.
  4. If a truly standalone display is needed, avoid these components and build custom markup.

Example fix

// before
<ToolHeader type={part.type} state={part.state} />
// after
<Tool toolCallId={part.toolCallId}>
  <ToolHeader type={part.type} state={part.state} />
</Tool>
Defensive patterns

Strategy: type-guard

Validate before calling

const canRenderToolParts = (el: React.ReactNode) =>
  React.isValidElement(el) &&
  (el.type === Tool || isDescendantOfTool(el))

Type guard

const toolContext = useContext(ToolContext)
if (toolContext === null) {
  // not inside <Tool>; render fallback or skip
  return null
}

Try / catch

try {
  const ctx = useTool() // note: hooks cannot be wrapped in try/catch; guard instead
} catch (e) {
  if (e instanceof Error && e.message.includes('Tool components')) {
    return <FallbackToolView />
  }
  throw e
}

Prevention

When it happens

Trigger: Rendering <ToolHeader>, <ToolContent>, <ToolInput>, <ToolToolbar> or calling useTool() without a wrapping <Tool> component (e.g. in a Storybook story, test, or custom tool UI).

Common situations: Refactoring that moves a tool sub-component out of its Tool parent; composing tool UI inside a chat message renderer that forgot the Tool wrapper; tests rendering sub-components in isolation.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/ad675ea4ad89b953. Report an issue: GitHub.

Appendix: source

Thrown at web-app/src/components/ai-elements/tool.tsx:57

import { parseCitationsFromToolOutput } from '@/lib/citation-parser'

/** Payloads shorter than this fit the collapsed box, so no expand control. */
const OUTPUT_EXPAND_THRESHOLD = 600

type ToolContextValue = {
  isOpen: boolean
  setIsOpen: (open: boolean) => void
  state: ToolUIPart['state']
  toolCallId?: string
  messageId?: string
}

const ToolContext = createContext<ToolContextValue | null>(null)

export const useTool = () => {
  const context = useContext(ToolContext)
  if (!context) {
    throw new Error('Tool components must be used within Tool')
  }
  return context
}

export type ToolProps = ComponentProps<typeof Collapsible> & {
  className?: string
  state: ToolUIPart['state']
  toolCallId?: string
  messageId?: string
  open?: boolean
  defaultOpen?: boolean
  onOpenChange?: (open: boolean) => void
}

export const Tool = memo(
  ({
    className,
    state,

View on GitHub (pinned to 7205d770c1)