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
- Set tool_call_id on each ToolExecutionDecision to the exact id of the corresponding tool call
- Verify tool_name on each decision matches the tool call's tool_name when ids are absent
- 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
- Always set tool_call_id on decisions from the originating tool call
- Regenerate decisions per run instead of caching across runs
- Keep tool_name consistent with the actual registered tool
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
- Expected one ToolExecutionDecision for each tool call, but r
- Tool execution requires at least one tool.
- `function` must be a synchronous function. '{function.__name
- `async_function` must be a coroutine function defined with `
- Hook function '{func.__name__}' must take a single parameter
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/c36b925a7431be60.
Report an issue: GitHub.