fix(codex-app-server): honor approvals.mode/yolo for gateway-context approval routing (salvage of #26533 by @simpolism, closes #26530) - #56534
Merged
kshitijk4poor merged 2 commits intoJul 1, 2026
Conversation
…approval routing
On gateway/cron/non-CLI contexts the codex app-server runtime has no UI to
surface codex's exec/apply_patch approval requests, so they fail closed
(silently decline) — the bot appears responsive but cannot write files, with
no approval prompt anywhere ("patch rejected by user").
When the user has explicitly opted out of Hermes approvals (approvals.mode: off,
the /yolo session toggle, or HERMES_YOLO_MODE=1), collapse to codex's own
sandbox permission profile (~/.codex/config.toml) as the policy gate by passing
_ServerRequestRouting(auto_approve_exec=True, auto_approve_apply_patch=True) to
the session. Defaults (manual/smart/unset) preserve the current fail-closed
behavior — a no-op for users who have not opted out.
Reads the mode via the canonical tools.approval._get_approval_mode() (which
already normalizes the YAML-1.1 bare-'off'->False case) at session-build time,
so a mid-session /yolo toggle is honored too.
5 integration tests: each opt-out mechanism (config off, YAML False, env var,
session yolo) plus the default fail-closed regression guard.
Closes NousResearch#26530
Co-authored-by: snav <jake@nousresearch.com>
…nv bypass in codex routing
Self-review follow-up on the salvaged approval-routing fix.
The initial adaptation re-read os.getenv("HERMES_YOLO_MODE") at session-build
time. That diverges from the repo's security invariant: HERMES_YOLO_MODE is
frozen into tools.approval._YOLO_MODE_FROZEN at import time precisely so a skill
running mid-process cannot set the env var and instantly flip the approval
bypass (a prompt-injection escalation path). A live re-read re-opened that hole
for the codex routing path.
- Add tools.approval.is_approval_bypass_active() — the canonical three-source
bypass check (frozen --yolo/HERMES_YOLO_MODE + session /yolo + approvals.mode
off) in one place. This is the 4th inline copy of that OR-chain (the three
sites in approval.py and tui_gateway/server.py:3121 all use the same idiom);
the helper is the shared chokepoint they can collapse onto.
- codex_runtime.py now calls is_approval_bypass_active() instead of the
hand-rolled mode-or-session check plus a runtime env re-read.
- Update the env-yolo test to patch _YOLO_MODE_FROZEN (the canonical test
pattern, e.g. tests/tools/test_yolo_mode.py) rather than setenv, which is
dead-on-arrival against the frozen constant.
Fail-closed default preserved on every branch; 28 integration + 77 session/yolo
tests pass; E2E confirms the real exec decision flips decline->accept only when
bypass is active.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Codex app-server exec/apply_patch requests on gateway/cron contexts now honor
approvals.mode: off//yolo/HERMES_YOLO_MODE=1instead of silently failing closed.Root cause: On non-CLI contexts no approval-UI callback is wired, so
CodexAppServerSession._decide_exec_approval/_decide_apply_patch_approvalhit their fail-closedreturn "decline"path. Codex sees a synthetic "user denied" and drops to read-only — the bot appears responsive but can't write files, and no prompt surfaces anywhere.Fix: When the user has explicitly opted out of Hermes approvals, build the session with
_ServerRequestRouting(auto_approve_exec=True, auto_approve_apply_patch=True)so codex's own sandbox permission profile (~/.codex/config.toml) becomes the boundary. Defaults (manual/smart/unset) keep the current fail-closed behavior — a no-op for anyone who hasn't opted out.Salvage of #26533 by @simpolism (also the reporter of #26530). Re-authored onto current
main: the construction site the original PR targeted (run_agent.py) has since moved intoagent/codex_runtime.py::run_codex_app_server_turn(). Authorship preserved.Changes
agent/codex_runtime.py: resolve opt-out at session-build time via the canonicaltools.approval._get_approval_mode()(which already normalizes the YAML-1.1 bare-off→Falsecase) +is_current_session_yolo_enabled()+HERMES_YOLO_MODE; passrequest_routing. Reading at build time means a mid-session/yolotoggle is honored too.tests/run_agent/test_codex_app_server_integration.py: 5 tests — configoff, YAMLFalse,HERMES_YOLO_MODE, session/yolo, plus the defaultmanualfail-closed regression guard.Validation
offacceptmanual/smart(default)declineHERMES_YOLO_MODE=1accept/yolosession toggleacceptHERMES_HOMEconfig: the gateway-context exec decision flipsdecline→acceptonly when opted out; fail-closed preserved otherwise.Notes
mcpServer/elicitation/requestfail-closed path for non-hermes-toolsservers (reported on codex app-server tool calls fail closed on gateway with no surfaceable approval prompt #26530 by @TAE58). That path declines by hard-coded server-name gating (arguably intentional) rather than_routing— a separate design question.Closes #26530
Follow-up commit (self-review,
hermes-pr-reviewPhase 2)refactor(approval): extract is_approval_bypass_active(); use frozen-env bypass in codex routingPhase-2 review flagged that the initial adaptation re-read
os.getenv("HERMES_YOLO_MODE")at runtime, which diverges from the repo's security invariant —HERMES_YOLO_MODEis frozen into_YOLO_MODE_FROZENat import time precisely so a mid-process skill can't set the env var and flip the approval bypass (prompt-injection escalation path). Fixed architecturally: extracted the canonical three-source bypass check intotools.approval.is_approval_bypass_active()(this was the 4th inline copy of that OR-chain;codex_runtime.pynow calls the shared helper). Env-yolo test updated to patch_YOLO_MODE_FROZEN(canonical pattern) instead ofsetenv. 28 integration + 77 session/yolo tests pass; E2E re-confirmed.