Skip to content

Python: [Bug]: DevUI function approvals are not scoped to the originating conversation #9177

Closed

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

  1. Create an AgentFrameworkExecutor.
  2. Track a function approval request with a request ID while representing conversation-a.
  3. Submit a function_approval_response containing the same request ID while processing conversation-b.
  4. Expect the response to be rejected.
  5. 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 deselected

Package 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.

Activity

  1. added
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 7, 2026
  2. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Oct 7, 2026
  3. github-actions commented on Oct 7, 2026

    @github-actions
    Contributor

    🤖 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_request and _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 raises ValueError.

    • Failing test: python/packages/devui/tests/devui/test_approval_validation.py::test_approval_request_must_be_bound_to_originating_conversation
    • Files examined: python/packages/devui/agent_framework_devui/_executor.py, python/packages/devui/tests/devui/test_approval_validation.py, python/packages/devui/pyproject.toml
    • Tests run: test_approval_validation.py baseline, test_approval_request_must_be_bound_to_originating_conversation, test_approval_validation.py with regression
    • Current version: 1.0.0b261002
  4. added
    devuiUsage: [Issues, PRs], Target: DevUI
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

devuiUsage: [Issues, PRs], Target: DevUIpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions