Allow PermissionRequest hooks to race with the terminal approval prompt
What variant of Codex are you using?
Codex CLI 0.148.0 on macOS. The requested behavior should live in codex-core so CLI, App, and IDE clients can share the same semantics.
What feature would you like to see?
Please add an opt-in mode for PermissionRequest hooks that keeps the normal user approval UI open while an external approval hook is waiting.
Today, Codex runs the blocking PermissionRequest hook before it creates the user approval request. A desktop approval UI can return allow/deny, but the terminal only shows Running PermissionRequest hook. If the hook returns no decision, the terminal prompt appears only afterwards.
The desired behavior matches Claude Code's permission relay:
- show the existing Codex approval prompt and the external approval UI concurrently;
- accept the first concrete allow/deny decision;
- close or invalidate the losing surface;
- if the hook errors, times out, or returns no decision, keep the terminal prompt actionable;
- if the terminal wins, cancel the hook process tree and emit a matching terminal hook-completion/cancellation event;
- preserve the current exclusive hook behavior by default.
An illustrative configuration shape would be an explicit opt-in on a PermissionRequest command hook, such as:
{
"type": "command",
"command": "external-approval-relay",
"timeout": 50,
"raceWithUserApproval": true
}
The exact configuration spelling is not important. The important contract is that only explicitly marked relay hooks race the user prompt. Existing policy hooks must remain blocking and keep their current precedence.
Suggested core behavior
- Run existing blocking/gating
PermissionRequesthooks first. - If they return no decision, start the normal reviewer approval request.
- Race that request against at most one explicitly configured relay hook.
- Commit exactly one decision.
- Cancel and clean up the losing path without converting cancellation into deny.
This can reuse the existing Session::request_user_approval oneshot path and the command hook process-tree guard. It should not inject terminal input or depend on tmux, AppleScript, Accessibility, or terminal-specific automation.
Acceptance criteria
- Existing
PermissionRequestconfiguration is behaviorally unchanged. - Hook allow first closes/resolves the terminal approval and allows once.
- Hook deny first closes/resolves the terminal approval and denies.
- Terminal allow/deny first terminates the relay hook process tree.
- Hook error, empty decision, and timeout leave the terminal prompt usable.
- Interruption and session shutdown resolve both paths exactly once.
- A stale or late hook result cannot decide a later request.
- Hook started/completed events remain paired.
- Linux, macOS, and Windows behavior is covered.
- Integration tests cover both completion orders with deterministic synchronization.
Why
Persistent desktop banners are useful when the terminal is hidden, but removing the familiar terminal approval is surprising. Keeping both surfaces live gives users a reliable local fallback without unsafe key injection or terminal scraping.
Related:
- #15311
- #16301
- #17563
- Claude Code permission relay: https://code.claude.com/docs/en/channels-reference#relay-permission-prompts
1 Comment
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action