moeru-ai/airi · error · Error
Assistant message does not contain tool call "${payload.tool
Error message
Assistant message does not contain tool call "${payload.toolCallId}" for "${payload.toolName}". What it means
The target assistant message exists but contains no tool call matching payload.toolCallId and payload.toolName. hasMatchingToolCall compares ids stored on the assistant message; replaceToolCallResult needs an exact match to splice the new result in, so a mismatch aborts the rerun before any tool executes.
Source
Thrown at packages/stage-ui/src/stores/tool-call-rerun.ts:88
/**
* Re-executes a stored tool call with supplied arguments and returns updated chat history.
*
* The resolver is injected so callers can choose the runtime-specific tool list
* without coupling this helper to app-local stores, Electron IPC, or browser state.
*/
export async function executeToolCallRerun<TToolset extends string = string>(
options: ExecuteToolCallRerunOptions<TToolset>,
): Promise<ChatHistoryItem[]> {
const { messages, payload } = options
const targetIndex = findTargetMessageIndex(messages, payload)
const targetMessage = messages[targetIndex]
if (targetMessage?.role !== 'assistant')
throw new Error('Tool call rerun target must be an assistant message.')
if (!hasMatchingToolCall(targetMessage, payload))
throw new Error(`Assistant message does not contain tool call "${payload.toolCallId}" for "${payload.toolName}".`)
const replaceTargetMessage = (result: ToolCallResultInput) => messages.map((item, itemIndex) => {
if (itemIndex !== targetIndex)
return item
return replaceToolCallResult(targetMessage, result)
})
const tools = await options.resolveTools()
const tool = tools.find(candidate => toolNameFrom(candidate) === payload.toolName)
if (tool == null) {
return replaceTargetMessage({
id: payload.toolCallId,
isError: true,
result: `Tool "${payload.toolName}" is not available for rerun in this runtime.`,
})
}
View on GitHub (pinned to 677329427f)
Solutions
- Refresh history and trigger the rerun from the current assistant message's tool-call block.
- Ensure the UI passes the toolCallId exactly as stored (no trimming or re-casing).
- If messages are transformed, preserve toolCallId values or invalidate rerun actions.
- Log the tool call ids present on the target message versus the payload to confirm the mismatch.
Defensive patterns
Strategy: type-guard
Validate before calling
const target = messages[findTargetMessageIndex(messages, payload)]
const hasCall = !!target && target.role === 'assistant'
&& listToolCallIds(target).includes(payload.toolCallId)
if (!hasCall) {
// hide/disable the rerun action
} Type guard
function assistantHasToolCall(m: ChatHistoryItem | undefined, toolCallId: string): m is ChatHistoryItem & { role: 'assistant' } {
return !!m && m.role === 'assistant' && listToolCallIds(m).includes(toolCallId)
} Try / catch
catch (e) {
const msg = errorMessageFrom(e) ?? ''
if (msg.includes('does not contain tool call'))
await reloadHistory()
else
throw e
} Prevention
- Bind rerun buttons to the toolCallId rendered from that exact message version.
- Invalidate pending rerun actions whenever the messages array is replaced.
- Diff toolCallIds when migrating or compacting history.
When it happens
Trigger: The assistant message was regenerated so its tool call got a new id while the payload kept the old one; streaming stopped mid-response leaving the message without tool-call parts; history sync or merge dropped/rekeyed tool call ids.
Common situations: Clicking a rerun affordance rendered from a pre-regeneration snapshot; providers emitting different toolCallId formats across versions; history dedupe rewriting ids.
Related errors
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/3f68beb29545691c.
Report an issue: GitHub.