Skip to content

Python: [Bug]: function_approval_response is dropped from the request under service-side storage, so an approval-paused run never resumes #7125

Description

@itjuba

Description

When an agent 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 is silently stripped from the outgoing request, so the approval decision never reaches the model. The model then re-issues the same approval request and the run pauses again — approval-gated tools can never execute under service-side storage.

Root cause: #5690 (the fix for the #3295 duplicate-item error) skips approval content under service-side storage, but the skip covers both the request and the response:

# agent_framework_openai/_chat_client.py — _prepare_message_for_openai
# (main @ c35a63e lines 1593-1595; identical in the 1.10.1 wheel)
case "function_approval_response" | "function_approval_request":
    if request_uses_service_side_storage:
        continue

Skipping function_approval_request is correct — the server already has it as part of the stored response. But function_approval_response is new client-side input the server has never seen, so dropping it loses the user's decision.

  • Expected: the continuation request carries the approval item (mcp_approval_response) and the approved tool executes.
  • Actual: the continuation request carries no approval item; the model re-issues the approval request → infinite approve/re-ask loop.

Suggested fix: under request_uses_service_side_storage, skip only function_approval_request and keep serializing function_approval_response.

Code Sample

Serialization-only repro (no network):

from agent_framework import Content, Message
from agent_framework_openai import OpenAIChatClient

client = OpenAIChatClient(model="gpt-4o", api_key="sk-not-used")

# the user's NEW approval decision, resuming an approval-paused run
approval = Message(
    "user",
    [
        Content.from_function_approval_response(
            approved=True,
            id="fa_1",
            function_call=Content.from_function_call(call_id="call_1", name="do_it", arguments={}),
        )
    ],
)

print(client._prepare_message_for_openai(approval, request_uses_service_side_storage=True))
print(client._prepare_message_for_openai(approval, request_uses_service_side_storage=False))

Output:

[]
[{'type': 'mcp_approval_response', 'approval_request_id': 'fa_1', 'approve': True}]

The same loss reproduces end-to-end against a live model with any approval_mode="always_require" tool once the request uses previous_response_id/conversation_id continuation.

Error Messages / Stack Traces

None — the item is dropped silently. The observable symptom is the model answering the approval with a fresh function_approval_request instead of running the tool.

Package Versions

agent-framework-core: 1.10.0 (also present at 1.11.0), agent-framework-openai: 1.10.0 (verified identical in 1.10.1, the latest release; still present on main @ c35a63e)

Python Version

Python 3.12

Additional Context

Is it a regression: yes — introduced by #5690 (first released in agent-framework-openai 1.4.0); before that the approval response was serialized under service-side storage. Known workaround: disable service-side storage for approval-gated agents, or re-append the stripped response after _prepare_message_for_openai.

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions