Skip to content

Python: keep the approval response under service-side storage so a paused run resumes#7133

Open
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/approval-response-service-side-storage
Open

Python: keep the approval response under service-side storage so a paused run resumes#7133
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/approval-response-service-side-storage

Conversation

@he-yufeng

Copy link
Copy Markdown
Contributor

Summary

Fixes #7125.

When an approval-gated run pauses for tool approval and the follow-up request continues a stored response (service-side storage: previous_response_id / conversation_id), the user's function_approval_response was silently stripped from the outgoing request, so the approval decision never reached the model. The model then re-issued the same approval request and the run paused again — approval-gated tools could never execute under service-side storage.

Root cause

_prepare_message_for_openai handled function_approval_response and function_approval_request in one case and skipped both under service-side storage. That is correct for the request (a server-issued item the server already has via the prior response, so replaying it inline duplicates it — #3295), but wrong for the response: the response is the user's decision, references the prior request by approval_request_id, and is not itself a server-stored item, so it must reach the model.

Fix

Split the two into separate cases: the approval request keeps the storage skip (same treatment as function_call), the approval response is always sent (same treatment as function_result directly above it).

Test

test_prepare_message_for_openai_keeps_approval_response_under_service_side_storage asserts the response survives with request_uses_service_side_storage=True (before the fix the prepared result is empty). The existing test_prepare_message_for_openai_with_function_approval_response already pins the storage-off serialization.

I verified the change by reading: the existing storage-off test confirms the mcp_approval_response serialization, and the fix mirrors the function_result branch directly above. A local pytest run was blocked by a workspace-install issue in my environment (agent_framework core not resolving under uv run), so the added test is exercised by CI rather than a local run.

…used run resumes

_prepare_message_for_openai handled function_approval_response and
function_approval_request in one case and skipped both under service-side
storage. That is right for the request (a server-issued item the server already
has, so replaying it inline duplicates it -- microsoft#3295) but wrong for the response:
the response is the user's approval decision, references the prior request by
approval_request_id, and is not itself a server-stored item, so stripping it
means the decision never reaches the model and an approval-paused run never
resumes (approval-gated tools can never execute under service-side storage).

Split the two into separate cases: the approval request keeps the storage skip
(like function_call), the approval response is always sent (like
function_result).

Fixes microsoft#7125
Copilot AI review requested due to automatic review settings July 15, 2026 19:08
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 15, 2026

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

This PR fixes a Python OpenAI client serialization bug that prevented approval-gated runs from resuming when continuing via service-side storage (previous_response_id / conversation_id). It ensures the user’s function_approval_response is still sent to the model under continuation requests, while continuing to skip replaying the server-issued function_approval_request to avoid duplicate-item errors.

Changes:

  • Split _prepare_message_for_openai handling so function_approval_request is skipped under service-side storage but function_approval_response is always serialized.
  • Added a regression test asserting the approval response is preserved when request_uses_service_side_storage=True.

Reviewed changes

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

File Description
python/packages/openai/agent_framework_openai/_chat_client.py Separates approval request vs response handling to prevent dropping the user’s approval decision under service-side storage.
python/packages/openai/tests/openai/test_openai_chat_client.py Adds a regression test to ensure approval responses survive continuation requests.

# not itself a server-stored item, so it must reach the model even
# under service-side storage; skipping it leaves the run paused
# forever (#7125). Same treatment as function_result above.
prepared = self._prepare_content_for_openai(

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.

Should we distinguish a fresh approval from one replayed by a HistoryProvider here? Providers store input messages and reload them with _attribution; on a later previous_response_id/conversation_id continuation this branch re-sends the already-consumed decision, regressing #3295's server/local replay boundary and making the existing test_prepare_messages_strips_approval_items_under_storage fail. Could we keep the fresh un-attributed response but continue skipping when request_uses_service_side_storage and replays_local_storage?

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]: function_approval_response is dropped from the request under service-side storage, so an approval-paused run never resumes

4 participants