Skip to content

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 into
NousResearch:mainfrom
kshitijk4poor:salvage/26533-codex-approval-mode
Jul 1, 2026
Merged

fix(codex-app-server): honor approvals.mode/yolo for gateway-context approval routing (salvage of #26533 by @simpolism, closes #26530)#56534
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/26533-codex-approval-mode

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Codex app-server exec/apply_patch requests on gateway/cron contexts now honor approvals.mode: off / /yolo / HERMES_YOLO_MODE=1 instead of silently failing closed.

Root cause: On non-CLI contexts no approval-UI callback is wired, so CodexAppServerSession._decide_exec_approval / _decide_apply_patch_approval hit their fail-closed return "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 into agent/codex_runtime.py::run_codex_app_server_turn(). Authorship preserved.

Changes

  • agent/codex_runtime.py: resolve opt-out at session-build time via the canonical tools.approval._get_approval_mode() (which already normalizes the YAML-1.1 bare-offFalse case) + is_current_session_yolo_enabled() + HERMES_YOLO_MODE; pass request_routing. Reading at build time means a mid-session /yolo toggle is honored too.
  • tests/run_agent/test_codex_app_server_integration.py: 5 tests — config off, YAML False, HERMES_YOLO_MODE, session /yolo, plus the default manual fail-closed regression guard.

Validation

approvals state auto_approve real exec decision
off True accept
manual / smart (default) False decline
HERMES_YOLO_MODE=1 True accept
/yolo session toggle True accept
  • 28/28 integration tests pass (23 existing + 5 new); 58/58 session tests pass.
  • E2E with real imports + temp HERMES_HOME config: the gateway-context exec decision flips declineaccept only when opted out; fail-closed preserved otherwise.

Notes

Closes #26530


Follow-up commit (self-review, hermes-pr-review Phase 2)

refactor(approval): extract is_approval_bypass_active(); use frozen-env bypass in codex routing

Phase-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_MODE is frozen into _YOLO_MODE_FROZEN at 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 into tools.approval.is_approval_bypass_active() (this was the 4th inline copy of that OR-chain; codex_runtime.py now calls the shared helper). Env-yolo test updated to patch _YOLO_MODE_FROZEN (canonical pattern) instead of setenv. 28 integration + 77 session/yolo tests pass; E2E re-confirmed.

…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>
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/openai OpenAI / Codex Responses API area/config Config system, migrations, profiles sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data P2 Medium — degraded but workaround exists labels Jul 1, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/openai OpenAI / Codex Responses API sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codex app-server tool calls fail closed on gateway with no surfaceable approval prompt

3 participants