Skip to content

Python: Fix duplicate function call on approval round-trip (#7267) - #7271

Merged
moonbox3 merged 4 commits into
microsoft:mainfrom
atty57:fix/7267-duplicate-function-call-on-approval-roundtrip
Jul 23, 2026
Merged

Python: Fix duplicate function call on approval round-trip (#7267)#7271
moonbox3 merged 4 commits into
microsoft:mainfrom
atty57:fix/7267-duplicate-function-call-on-approval-roundtrip

Conversation

@atty57

@atty57 atty57 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

Approving a run_skill_script tool call in the Agent Inspector left the function
call 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 — SkillsProvider is 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_call from its function_approval_request
    wrapper, 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_call item and its mcp_approval_request
    item 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_call exactly once,
    paired with its result.

    Verified with a full-stack ResponsesHostServer + SkillsProvider repro
    (load_skill approval, then run_skill_script approval). Outgoing history on
    the final turn, before and after:

    before                                after
    ------------------------------------  ------------------------------------
    user      [text]                      user      [text]
    assistant [function_call   call_1]    assistant [function_call   call_1]
    assistant [function_call   call_1]    tool      [function_result call_1]
    tool      [function_result call_1]    assistant [function_call   call_2]
    assistant [function_call   call_2]    tool      [function_result call_2]
    assistant [function_call   call_2]
    tool      [function_result call_2]
    

    Regression test added in
    python/packages/core/tests/core/test_function_invocation_logic.py; it fails on
    main with ['call_1', 'call_1'] == ['call_1']. Core suites and all 192
    agent-framework-foundry-hosting tests pass.

  • What do you want reviewers to focus on?

    Two things I could not close out locally, both worth a second opinion:

    1. I could not reproduce the reported Inspector hang — against a mock chat
      client the registered script_runner was invoked on both the pre-fix and
      post-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.
    2. The duplication also affects load_skill's call, so on a strict reading the
      reported 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

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow automatically keeps the label and title prefix in sync.

…#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>
Copilot AI review requested due to automatic review settings July 22, 2026 23:49
@atty57
atty57 temporarily deployed to github-app-auth July 22, 2026 23:49 — with GitHub Actions Inactive
@atty57
atty57 temporarily deployed to github-app-auth July 22, 2026 23:49 — with GitHub Actions Inactive
@atty57
atty57 temporarily deployed to github-app-auth July 22, 2026 23:49 — with GitHub Actions Inactive
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Jul 22, 2026
@atty57
atty57 temporarily deployed to github-app-auth July 22, 2026 23:49 — with GitHub Actions Inactive
@atty57

atty57 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_results to dedupe restored function_call items 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.

@atty57
atty57 temporarily deployed to github-app-auth July 22, 2026 23:52 — with GitHub Actions Inactive
Comment thread python/packages/core/agent_framework/_tools.py
Refactor approval handling logic to improve clarity and maintainability.
@atty57
atty57 temporarily deployed to github-app-auth July 23, 2026 03:23 — with GitHub Actions Inactive
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@github-actions

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _tools.py11688292%227–228, 405, 407, 420, 445–447, 455, 473, 487, 494, 501, 524, 526, 533, 541, 676, 710–712, 715–717, 719, 725, 776–778, 803, 829, 833, 871–873, 877, 1050, 1062, 1069–1072, 1093, 1101, 1115–1117, 1467, 1562, 1590, 1612, 1620, 1678, 1725, 1732–1733, 1792, 1796, 1849, 1910–1911, 1922, 1994, 2008, 2011, 2024, 2027, 2050, 2057, 2067, 2071, 2174, 2222, 2244, 2300, 2379, 2576, 2642–2643, 2809–2810, 2901
TOTAL45265445190% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9369 33 💤 0 ❌ 0 🔥 2m 26s ⏱️

@moonbox3
moonbox3 added this pull request to the merge queue Jul 23, 2026
Merged via the queue into microsoft:main with commit d98ac29 Jul 23, 2026
37 checks passed
@atty57
atty57 deleted the fix/7267-duplicate-function-call-on-approval-roundtrip branch July 27, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: run_skill_script approval handshake leaves function call unanswered in local Inspector (Responses API)

4 participants