Repository navigation
Description
Description
What happened?
DevUI tracks pending function approval requests by request_id, but the pending approval is not bound to the conversation that originated it.
AgentFrameworkExecutor._track_approval_request() stores the server-side function call information in _pending_approvals, keyed by request_id. When a function_approval_response is later processed, the pending approval is retrieved using that request_id.
Because the originating conversation is not part of the validation, an approval response submitted while processing a different conversation can match and consume a pending approval issued for another conversation.
I reproduced this with a regression test using two different conversation IDs. An approval request is created for conversation-a, then an approval response using the same request ID is submitted from conversation-b. The response is accepted rather than rejected.
What did you expect to happen?
A pending function approval should be bound to the conversation that caused the approval request to be issued.
An approval response should only be accepted when both the request ID and originating conversation match. A response from another conversation should be rejected.
Steps to reproduce the issue
- Create an
AgentFrameworkExecutor. - Track a function approval request with a request ID while representing
conversation-a. - Submit a
function_approval_responsecontaining the same request ID while processingconversation-b. - Expect the response to be rejected.
- Observe that the response is accepted because pending approval lookup is based on the request ID without validating the originating conversation.
The regression test fails on the current implementation with:
Failed: DID NOT RAISE ValueError
The focused test result before applying conversation scoping was:
1 failed, 9 deselected
Code Sample
def test_approval_request_must_be_bound_to_originating_conversation(
executor: AgentFrameworkExecutor,
) -> None:
executor._track_approval_request(
{
"type": "response.function_approval.requested",
"request_id": "req_cross_conversation",
"function_call": {
"id": "call_sensitive",
"occurrence_id": "af-call-sensitive",
"name": "sensitive_tool",
"arguments": {"resource": "conversation-a-secret"},
},
},
conversation_id="conversation-a",
)
conversation_b = AgentFrameworkRequest(
model="test-agent",
input=_make_approval_response_input(
request_id="req_cross_conversation",
approved=True,
),
conversation="conversation-b",
)
with pytest.raises(
ValueError,
match="did not contain any supported message content",
):
executor._convert_input_to_chat_message(
conversation_b.input,
conversation_id="conversation-b",
)Error Messages / Stack Traces
FAILED packages/devui/tests/devui/test_approval_validation.py::test_approval_request_must_be_bound_to_originating_conversation
E Failed: DID NOT RAISE ValueError
1 failed, 9 deselectedPackage Versions
agent-framework-devui: current main
Python Version
Python 3.14.8
Additional Context
The existing DevUI approval-validation tests already validate unknown/forged request IDs, use of server-stored function-call data, and prevention of approval replay.
The missing boundary appears to be conversation ownership: the server validates that an approval request was issued, but does not validate that it was issued for the conversation submitting the response.
The pending approval registry is shared by the executor and currently keyed by request_id. Binding each pending approval to its originating conversation allows a response from a different conversation to be rejected while preserving the existing request-ID and replay protections.
A regression test reproducing the issue has been added locally. After adding conversation binding, the full approval-validation test file passes:
10 passed in 0.05s
Ruff also passes on the modified executor.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Oct 7, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
The bug is in
python/packages/devui/agent_framework_devui/_executor.py::_track_approval_requestand_convert_openai_input_to_chat_message(lines 133-145 and 793-845). It occurs when one conversation issues an approval and another submits the same request ID, because the executor-wide registry stores no conversation ownership. Minimal repro: track an approval while representing conversation A, convert a matching approval response from conversation B, and assert that conversion raisesValueError.python/packages/devui/tests/devui/test_approval_validation.py::test_approval_request_must_be_bound_to_originating_conversation1.0.0b261002