Skip to content

Bridge RequestUserInput events to ACP permission prompts#199

Open
c0st1nus wants to merge 1 commit intozed-industries:mainfrom
c0st1nus:main
Open

Bridge RequestUserInput events to ACP permission prompts#199
c0st1nus wants to merge 1 commit intozed-industries:mainfrom
c0st1nus:main

Conversation

@c0st1nus
Copy link

Summary

This fixes a deadlock in codex-acp when codex_core requests pre-tool user input for MCP approvals.

Previously, EventMsg::RequestUserInput was treated as an unexpected event in the ACP bridge. When that happened, core would wait indefinitely for Op::UserInputAnswer, so the turn could stall before any McpToolCallBegin event was emitted.

This change bridges option-based RequestUserInput events into ACP request_permission(...) prompts, then maps the selected ACP option back into the RequestUserInputResponse shape expected by core.

What changed

  • Added handling for EventMsg::RequestUserInput in src/thread.rs.
  • Reused the existing ACP permission-request flow instead of introducing a separate interaction channel.
  • Added a new pending interaction variant for user-input requests so the response can be resolved through the same async path already used for approvals.
  • Converted single-question, option-based RequestUserInput prompts into ACP request_permission(...) requests.
  • Mapped the selected ACP option back to the original answer label expected by codex_core, then submitted Op::UserInputAnswer.
  • Added a fail-closed fallback for unsupported RequestUserInput shapes:
    if the request cannot be represented through ACP permission options, codex-acp now returns an empty response instead of hanging indefinitely.
  • Updated the thread test stub to accept Op::UserInputAnswer.

Why this fixes the issue

For MCP approvals, codex_core may request user input before it emits McpToolCallBegin. If the ACP bridge drops that request, core waits forever and the client only sees early events such as usage_update.

With this patch:

  • RequestUserInput is surfaced to the ACP client as a permission prompt.
  • The user's selection is translated back into Op::UserInputAnswer.
  • Core can resume the approval flow and continue to the actual MCP tool call.

Verification

  • Added a focused unit test covering the happy path:
    test_request_user_input_uses_permission_request
  • Added a focused unit test covering unsupported input shape fallback:
    test_request_user_input_without_options_cancels_without_permission_prompt
  • Ran:
    cargo test test_request_user_input_
  • Ran:
    cargo fmt --check

Notes

  • This change intentionally targets the option-based RequestUserInput shape used by the MCP approval flow.
  • Unsupported request shapes now fail closed instead of deadlocking.
  • This PR does not address the separate StreamError silent-hang path.

Copilot AI review requested due to automatic review settings March 23, 2026 16:50
Copy link

Copilot AI left a comment

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 deadlock in codex-acp by handling EventMsg::RequestUserInput events, translating option-based user-input prompts into ACP request_permission(...) prompts, and mapping the selected option back into Op::UserInputAnswer so codex_core can resume MCP approval flow.

Changes:

  • Added a new pending-interaction variant to track user-input requests and resolve them through the existing async permission-request flow.
  • Implemented RequestUserInput event handling that converts option-based prompts into ACP permission requests and submits Op::UserInputAnswer on resolution.
  • Added unit tests for the supported option-based path and the fail-closed unsupported-shape fallback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@c0st1nus
Copy link
Author

Manual verification update:

I tested this patch in Zed against the original failing scenario using a locally built codex-acp and ChatGPT account auth (no API key), with Playwright MCP enabled.

Before this change, Codex ACP would accept the prompt, emit usage_update, and then stall indefinitely without emitting any tool_call, message chunk, or stop reason.

After this change, the same flow now proceeds correctly:

  • the pre-tool approval path is surfaced through ACP
  • the pending user-input request is resolved
  • the Playwright tool call is emitted normally
  • the minimal reproduction completes successfully

I verified it with the original repro shape:
Do not explain steps. Do not write a plan. Immediately use the Playwright tool. Open about:blank, set document.body.innerText = "ok" and return only the result.

Result: Playwright now runs successfully in Zed and returns the expected output instead of hanging before tool invocation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants