Python: Fix duplicate function call on approval round-trip (#7267) - #7271
Merged
moonbox3 merged 4 commits intoJul 23, 2026
Merged
Conversation
…#7267) `_replace_approval_contents_with_results` deduped restored function calls against only the message currently being scanned. On an approval round-trip the hosting layer replays the stored `function_call` item and its `mcp_approval_request` item as two separate assistant messages, so the per-message check never fired and the approval request restored a second copy of the call. Only one copy received the function result; the orphaned copy was left unanswered, which the Responses API rejects with "No tool output found for function call call_<id>". Collect existing call ids across all messages instead, and add a restored call to that set so two approval requests for the same call cannot both expand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
@microsoft-github-policy-service agree |
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes an approval round-trip edge case in the Python core tooling pipeline where a function_call could be restored twice (when the hosting layer replays the stored call and its approval request in separate assistant messages), leaving one copy orphaned without a matching function_result and causing the Responses API to reject the next turn.
Changes:
- Update
_replace_approval_contents_with_resultsto dedupe restoredfunction_callitems against call IDs collected across all messages (not per-message), and record newly restored call IDs to prevent multiple expansions. - Add a regression test covering the “call in one assistant message, approval request in another” replay shape.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| python/packages/core/agent_framework/_tools.py | Fixes call-id dedupe scope so approval request expansion cannot introduce duplicate function_calls across messages. |
| python/packages/core/tests/core/test_function_invocation_logic.py | Adds a regression test ensuring only one function_call remains and the result is properly paired after an approval round-trip. |
moonbox3
reviewed
Jul 23, 2026
Refactor approval handling logic to improve clarity and maintainability.
Updated the test to allow reused call IDs after completion, ensuring that a completed call does not suppress later approval requests with the same ID. Adjusted assertions to reflect the new behavior.
eavanvalkenburg
approved these changes
Jul 23, 2026
Contributor
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
moonbox3
approved these changes
Jul 23, 2026
atty57
deleted the
fix/7267-duplicate-function-call-on-approval-roundtrip
branch
July 27, 2026 22:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
Approving a
run_skill_scripttool call in the Agent Inspector left the functioncall unanswered: the tool block showed a red ✗ and the next conversation turn was
rejected by the Responses API with
400 - No tool output found for function call call_<id>.The same defect affects any approval round-trip served through a hosting layer,
not just skills —
SkillsProvideris simply the configuration that surfaces it,because it puts an approval-gated tool on the default path.
Description & Review Guide
What are the major changes?
One scoping fix in
_replace_approval_contents_with_results(
python/packages/core/agent_framework/_tools.py).The function restores a
function_callfrom itsfunction_approval_requestwrapper, deduping against calls already present so it does not add a second
copy. That dedupe set was rebuilt inside the per-message loop, so it only ever
saw the message currently being scanned. On an approval round-trip the hosting
layer replays the stored
function_callitem and itsmcp_approval_requestitem as two separate assistant messages — so the check never fired and the
approval request restored a duplicate of the call.
Only one copy was matched to the function result. The orphaned copy went back
to the service unanswered, which is exactly what the 400 reports.
The fix collects existing call ids across all messages, and adds a restored
call to that set so two approval requests for the same call cannot both expand.
What is the impact of these changes?
Not a breaking change; no public API or signature changes. Conversation history
sent on an approval round-trip now contains each
function_callexactly once,paired with its result.
Verified with a full-stack
ResponsesHostServer+SkillsProviderrepro(
load_skillapproval, thenrun_skill_scriptapproval). Outgoing history onthe final turn, before and after:
Regression test added in
python/packages/core/tests/core/test_function_invocation_logic.py; it fails onmainwith['call_1', 'call_1'] == ['call_1']. Core suites and all 192agent-framework-foundry-hostingtests pass.What do you want reviewers to focus on?
Two things I could not close out locally, both worth a second opinion:
client the registered
script_runnerwas invoked on both the pre-fix andpost-fix paths. This change fixes the duplication that produces the 400;
whether the hang is entirely downstream of it is unconfirmed without the
live Foundry API.
load_skill's call, so on a strict reading thereported flow should have failed one turn earlier than the issue describes.
That may mean the service tolerates some duplicate shapes, or that a second
factor is involved.
Related Issue
Fixes #7267
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow automatically keeps the label and title prefix in sync.