moeru-ai/airi · error · Error
Tool invocation " " is missing or ambiguous.
Error message
Tool invocation "${invocationId}" is missing or ambiguous. What it means
findInvocation locates a tool invocation within a message's generationTranscript rounds by callId, optionally narrowed by invocationId. When invocationId is given, exactly one matching candidate must exist; zero or multiple matches make the reference ambiguous or nonexistent, so it throws rather than guessing which invocation to rerun.
Solutions
- Refresh the invocation id from the current message transcript before rerunning (re-read message.generationTranscript).
- If the callId legitimately appears multiple times, omit invocationId to get the ambiguity error and then select a specific invocation id.
- Verify the invocationId belongs to the same message whose transcript you pass in.
- Fix data producing duplicate invocation ids if the transcript was imported/generated incorrectly.
Example fix
// before
findInvocation(message, { callId, invocationId: 'stale-id' })
// after
const invocation = findInvocation(freshMessage, { callId, invocationId: currentInvocationId })
if (!invocation) return // call no longer present Defensive patterns
Strategy: try-catch
Validate before calling
const transcript = message.generationTranscript?.rounds.flatMap(r => r.toolInvocations.filter(c => c.callId === callId)) ?? [] if (transcript.length === 0) return null
Try / catch
try {
const found = findInvocation(message, { callId, invocationId })
} catch (e) {
if (e.message.includes('missing or ambiguous')) {
refreshTranscript(message.id)
return
}
throw e
} Prevention
- Always read invocation ids from the latest transcript state, never cache across regenerations.
- Scope callId lookups to the exact message the id came from.
- Ensure transcript writes keep invocation ids unique within a message.
When it happens
Trigger: Calling findInvocation (via target/invocation selectors) with an invocationId that no longer exists in the transcript, matches multiple stored invocations, or when the message has no generationTranscript/rounds at all.
Common situations: Rerunning a tool call after the session was edited/regenerated so ids changed; passing an invocation id copied from another message; transcripts containing duplicate invocation ids after a buggy import; referencing a call from a cleared history.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Tool call " " is ambiguous. Select an invocation.
- Assistant message does not contain tool call
- JSON.stringify(result)
- [llm-streaming-control] handler failed
- Select this tool before running it again.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/21aaf1ad33749a8b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-ui/src/stores/tool-call-rerun.ts:50
}
interface ExecuteToolCallRerunOptions<TToolset extends string = string> {
messages: ChatHistoryItem[]
payload: ToolCallRerunPayload<TToolset>
resolveTools: () => Promise<Tool[]>
}
type ToolCallResultInput = Omit<ChatSlicesToolCallResult, 'type'>
type ToolExecuteOptions = NonNullable<Parameters<Tool['execute']>[1]>
function findInvocation(message: ChatAssistantMessage, callId: string, invocationId?: string) {
const candidates = message.generationTranscript?.rounds.flatMap((round, roundIndex) =>
round.toolInvocations.filter(call => call.callId === callId).map(call => ({ call, roundIndex })),
)
if (invocationId !== undefined) {
const matches = candidates?.filter(candidate => candidate.call.id === invocationId)
if (!candidates || matches?.length !== 1)
throw new Error(`Tool invocation "${invocationId}" is missing or ambiguous.`)
return { ...matches[0], occurrence: candidates.indexOf(matches[0]) }
}
if (candidates && candidates.length > 1)
throw new Error(`Tool call "${callId}" is ambiguous. Select an invocation.`)
return candidates?.[0] ? { ...candidates[0], occurrence: 0 } : undefined
}
/**
* Replaces one execution and its display results, preserving their order.
* Native continuation for this round and later rounds becomes stale after the edit.
* Repeated provider call ids require an AIRI invocation id.
*/
export function replaceToolCallResult(message: ChatAssistantMessage, result: ToolCallResultInput, invocationId?: string): ChatAssistantMessage {
const target = findInvocation(message, result.id, invocationId)
const occurrence = target?.occurrence ?? 0
const toolResult = { id: result.id, isError: result.isError, result: result.result }
const resultSlice: ChatSlicesToolCallResult = { type: 'tool-call-result', ...toolResult }
let generationTranscript = message.generationTranscriptView on GitHub (pinned to 438a067dde)