deepset-ai/haystack · error

No unused ToolExecutionDecision matches tool call {tc.tool_n

Error message

No unused ToolExecutionDecision matches tool call {tc.tool_name!r} with ID {tc.id!r}. Set tool_call_id to match decisions to tool calls reliably.

What it means

When matching decisions to tool calls, the code first looks up by tool_call_id, then falls back to unused decisions by tool_name. If no remaining decision matches either key for a given tool call, it cannot safely determine what the user decided, so a ValueError is raised advising to set tool_call_id.

Source

Thrown at haystack/hooks/human_in_the_loop/strategies.py:591

    def make_assistant_message(chat_message: ChatMessage, tool_calls: list[ToolCall]) -> ChatMessage:
        return ChatMessage.from_assistant(
            text=chat_message.text,
            meta=chat_message.meta,
            name=chat_message.name,
            tool_calls=tool_calls,
            reasoning=chat_message.reasoning,
        )

    new_tool_call_messages = []
    rejection_messages = []
    for chat_msg in tool_call_messages:
        new_tool_calls = []
        for tc in chat_msg.tool_calls or []:
            ted = decision_by_id.pop(tc.id or "", None)
            if ted is None:
                ted = decision_by_name.pop(tc.tool_name, None)
            if ted is None:
                raise ValueError(
                    f"No unused ToolExecutionDecision matches tool call {tc.tool_name!r} with ID {tc.id!r}. "
                    "Set tool_call_id to match decisions to tool calls reliably."
                )

            classified_decision = _classify_decision(tool_call=tc, decision=ted)
            final_args = ted.final_tool_params or {}

            if classified_decision == "reject":
                # rejected tool call
                tool_result_text = ted.feedback or REJECTION_FEEDBACK_TEMPLATE.format(tool_name=tc.tool_name)
                rejection_messages.extend(
                    [
                        make_assistant_message(chat_msg, [tc]),
                        ChatMessage.from_tool(tool_result=tool_result_text, origin=tc, error=True),
                    ]
                )
                continue

View on GitHub (pinned to e318778c9b)

Solutions

  1. Set tool_call_id on each ToolExecutionDecision to the exact id of the corresponding tool call
  2. Verify tool_name on each decision matches the tool call's tool_name when ids are absent
  3. Regenerate decisions from the same tool call messages instead of reusing stale ones

Example fix

// before
ToolExecutionDecision(tool_name="search", ...)  # no tool_call_id, name mismatch
// after
ToolExecutionDecision(tool_name="search", tool_call_id=tool_call.id, ...)
Defensive patterns

Strategy: validation

Validate before calling

ids = {tc.id for m in tool_call_messages for tc in (m.tool_calls or []) if tc.id}
for d in tool_execution_decisions:
    assert d.tool_call_id in ids, f"decision for {d.tool_name!r} has unknown tool_call_id {d.tool_call_id!r}"

Type guard

def decision_matches(decision, tool_call) -> bool:
    return bool(decision.tool_call_id and decision.tool_call_id == tool_call.id) or \
        decision.tool_name == tool_call.tool_name

Try / catch

try:
    result = _apply_tool_execution_decisions(messages, decisions)
except ValueError as e:
    if "No unused ToolExecutionDecision" in str(e):
        decisions = [replace(d, tool_call_id=tc.id) for d, tc in zip(decisions, all_tool_calls)]
        result = _apply_tool_execution_decisions(messages, decisions)
    else:
        raise

Prevention

When it happens

Trigger: Applying decisions whose tool_call_id values do not match the tool calls' ids and whose tool_name values differ (or were already consumed) — e.g. decisions built for a different request, stale decisions reused across runs, or names altered by modification.

Common situations: Reusing a saved decision list in a new Agent run where tool call ids changed; custom strategy returning decisions with wrong/renamed tool_name; multiple same-name tool calls with ambiguous id-less decisions.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/c36b925a7431be60. Report an issue: GitHub.