fix(codex): bridge gateway approvals safely - #66925
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real gateway gap. Current origin/main only passes the CLI approval callback into CodexAppServerSession at agent/codex_runtime.py:643-650, while missing callbacks decline Codex exec and file-change requests at agent/transports/codex_app_server_session.py:875-937.
Problems
tools/approval.py:2137-2140returns a gateway"session"choice without callingapprove_session(). The existing gateway path persists that choice at current-maintools/approval.py:2804-2805; without the same persistence, every later Codex approval will prompt again.tests/run_agent/test_codex_app_server_integration.py:86-175tests the resolver directly, not therun_codex_app_server_turn()construction path that must carry gateway session context and notifier registration.
Suggested changes
- Persist a resolved
"session"choice undercodex_app_server; keep permanent approval disabled. - Add a lifecycle-level test from gateway notifier registration through a Codex server approval request and response.
Automated hermes-sweeper review.
| if decision.get("notify_failed") or not decision.get("resolved"): | ||
| return "deny" | ||
|
|
||
| choice = decision.get("choice") or "deny" |
There was a problem hiding this comment.
A session decision is returned but never persisted. The normal gateway approval path calls approve_session(session_key, pattern_key) before returning success; please do the same here so one session approval covers subsequent Codex requests.
There was a problem hiding this comment.
Fixed — prompt_gateway_approval() now calls approve_session() (and approve_permanent()/save_permanent_allowlist() for the true-permanent branch) after a resolved session/always choice, matching the exact persistence convention every other approval call site in this module already uses. Added a lifecycle test that drives a real run_conversation() turn so codex_runtime constructs an actual CodexAppServerSession with the gateway-resolved callback wired in exactly as production does, and asserts a second exec-approval request for the same pattern does NOT re-prompt the notifier, proving persistence through the real construction path rather than just the resolver in isolation. Pushed in 78657ed28.
|
Addressed both points from the hermes-sweeper review: Persistence bug (real, confirmed): Test coverage gap: added Test results: Pushed to this branch in 78657ed28. Additional corroboration: I also carry an independently-written approval bridge for this exact same use case (Codex exec/apply_patch → gateway approval UI) on a separate deployment, predating this PR. Auditing it against this review's finding turned up the identical class of bug — it checks a notifier and awaits a decision, but never persisted a resolved approval either. Fixed there with the same approach ( |
SummaryFour PRs address the missing Codex app-server approval path in gateway sessions: #25889 adds a transport fallback with a separate wait loop, #27636 adds a runtime callback bridge with another separate wait loop, #43217 is a closed shared-waiter implementation mixed with Telegram cache-path changes, and #66925 combines the runtime bridge with the shared waiter, policy floors, forced redaction, and session-choice persistence. Related pull requests
Duplicates#25889 and #27636 substantially duplicate the same Codex-to-gateway bridge through different wiring points; #43217 is an earlier closed implementation of the shared core, while #66925 consolidates that work with additional policy and redaction safeguards. Suggested consolidationKeep #66925 open with a salvage path: retain its shared-waiter bridge, policy floors, forced redaction, session persistence, and lifecycle coverage while maintainers validate the updated head. Close #25889 as superseded by #66925 with the author's stated consent, close #27636 as a duplicate of #66925 despite its keep_open verdict because #66925's diff implements the verdict's frozen-bypass and shared-interrupt requirements, and keep #43217 closed as a historical reference. Cross-PR triage: Reviewed 4 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 63 kB of PR diffs, 6 kB of issue/PR text, 11 kB of discussion (15 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Address hermes-sweeper review on NousResearch#66925: - prompt_gateway_approval() checked is_approved(session_key, pattern_key) before prompting, but never called approve_session() after a "session" (or demoted "always") choice was resolved — so every subsequent Codex exec/apply_patch request in the same session re-prompted the user despite them having already picked "approve for this session". Fixed to match the identical persistence convention already used by every other approval call site in this module. - Added a lifecycle-level test that drives a real run_conversation() turn so codex_runtime constructs an actual CodexAppServerSession with the gateway-resolved approval_callback wired in exactly as production does, simulates a mid-turn Codex exec-approval request through that stored callback, and asserts a second request for the same pattern does NOT re-prompt the notifier (proving the persistence fix through the real construction path, not just the resolver in isolation). 313 tests in tests/tools/test_approval.py + tests/run_agent/test_codex_app_server_integration.py: 312 passed, 1 pre-existing/unrelated macOS /tmp-vs-/private/tmp failure (already documented in the PR description as failing on clean main too).
78657ed to
4062a9d
Compare
Summary
Bridge Codex app-server exec and file-change approval requests into Hermes' existing gateway approval queue when a live gateway session is available.
This preserves CLI approval behavior, keeps cron/non-interactive contexts fail-closed, and reuses the shared timeout, interrupt, cleanup, heartbeat, and approval-hook machinery.
Problem
run_codex_app_server_turn()currently only resolves the CLI approval callback. Gateway sessions therefore have no callback for Codexitem/commandExecution/requestApprovalanditem/fileChange/requestApprovalrequests, so they are silently declined even though the user is present on Telegram, Discord, or Desktop.This is the remaining interactive-gateway case discussed in #26530 and the draft approach in #27636. The merged YOLO/mode-off behavior does not provide an interactive approval bridge for manual or smart mode.
Implementation
prompt_gateway_approval(), which:approvals.denyfloors before any bypass;_await_gateway_decision();auto_approve_execandauto_approve_apply_patchshortcuts.Safety properties
sudo -Sguessing and user-defined deny rules remain unconditional.force=True, independent ofsecurity.redact_secrets.Tests
The deselected test is
TestDetectDangerousRm::test_nonrecursive_verification_artifact_cleanup_is_not_dangerous, which also fails on cleanmainon macOS because/tmpresolves through/private/tmp; it is unrelated to this change.New regression coverage includes:
Related work