feat(routing): route Codex "Approve for me" reviews and add codex_approve_all - #872
Open
elyasmnvidian wants to merge 2 commits into
Open
elyasmnvidian wants to merge 2 commits into
elyasmnvidian wants to merge 2 commits into
Conversation
Signed-off-by: Greg Clark <grclark@nvidia.com> chore: cleanup Signed-off-by: Greg Clark <grclark@nvidia.com> chore: cleanup Signed-off-by: Greg Clark <grclark@nvidia.com>
messiaen
force-pushed
the
grclark/mac-deamon
branch
from
September 29, 2026 18:10
d6ce102 to
c559654
Compare
|
…rove_all Signed-off-by: Elyas Mehtabuddin <emehtabuddin@nvidia.com>
elyasmnvidian
force-pushed
the
emehtabuddin/switch-1630-codex-approve-for-me-switchyard
branch
from
October 1, 2026 20:07
ee44fbc to
3e9a724
Compare
messiaen
force-pushed
the
grclark/mac-deamon
branch
3 times, most recently
from
October 2, 2026 00:02
c871c35 to
042211d
Compare
This branch has not been deployed
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.
What
When "Approve for me" is on and Codex runs through Switchyard, Codex declines every action that needs approval. Codex asks a reviewer model to approve each of these actions. Unless Codex is logged in with an OpenAI API key, it sends the review request to the model ID
codex-auto-review, even when it has no OpenAI login at all. Before this PR, neitherexamples/run_codex.shnor the config thatscripts/macos/install.shwrites had a route with that ID. So the server returned 404model_not_found, and Codex treated the failed review as a denial.This PR adds a
codex-auto-reviewroute to both configs and adds a route type,codex_approve_all. The route's config decides what answers the review requests:passthroughroute sends the review request unchanged tocodex-auto-reviewon the ChatGPT backend. Thechatgpt_backendclient setsforward_auth = true, so Switchyard forwards the caller's login. Both configs use this setup.targetat any other target. Codex reads the reviewer's final message as a JSON verdict, so the model must follow a JSON output schema.codex_approve_allanswers every review request with Codex's allow verdict and calls no model. Codex then runs every action that needs approval, including commands that ask to run outside the sandbox.A config without a
codex-auto-reviewroute behaves as before: the server returns 404, and Codex declines the action.codex_approve_allaccepts only the keys that every route takes (id,type,context_window,tool_calling,reasoning,vision), so the server refuses to start iftargetis left in:codex_approve_allreuses the reply builder fromnoop, which this PR moves tofixed_replyincrates/libsy/src/algorithms/util.rs. The reply is streamed or buffered to match the request, and the route keeps no state.Menu bar prices. The menu bar hides a period's dollar figures when any model seen in that period has no price. In the installer's setup, the routing log records each review under
codex-auto-review, so the installer now prices that model ID at thebaseline_modelrates. The dollars saved stay the same, and the percentage drops a little (details below). The installer never overwrites an existingcomposite.tomlormenubar.toml, so an existing install needs the route and the price added by hand.Why
Codex picks the reviewer model ID from its login type, not from Switchyard.
codex-auto-reviewis a hidden model in Codex's built-in catalog. Switchyard'sGET /v1/modelsreturns"models": []on purpose, so Codex keeps that catalog and sends the ID even though Switchyard does not list it. With an OpenAI API-key login, Codex usesgpt-5.6-lunainstead. Neither config adds a route for that ID. The server README explains how to add one, and warns that the route then receives every Codex request forgpt-5.6-luna, not only review requests.The alternative is a Codex setting. Codex can move reviews to another model only through a full replacement model catalog (
model_catalog_json), in which the session model's entry setsauto_review_model_override. I tested this: with that override and no reviewer route in Switchyard, the review request went to theswitchyardroute, and the command ran. But the bundled catalog is about 420 KB and changes with Codex releases, so a Switchyard route is the simpler fix.Notes for reviewers
This PR is stacked on #863 because it changes the config files that the macOS installer writes. Start with
crates/libsy/src/algorithms/codex_approve_all.rsand theCodexApproveAllvariant incrates/switchyard-runner/src/algorithm.rs.The new route also changes
GET /v1/models. The server sorts route IDs, andcodex-auto-reviewsorts beforeswitchyard, sodefault_modelandfirst_idchange fromswitchyardtocodex-auto-review, and the startup banner's examplecurlusescodex-auto-review. I found no client in this repo or in Codex that readsdefault_model, and this PR does not change how the server picks it.All live runs below used this branch rebased onto the current #863. Every Codex run used
codex exec --approve-for-me(Codex 0.152.0), a temporaryCODEX_HOMEwith no OpenAI login, and a prompt that makes Codex ask for network access to runcurl https://example.com. No run sent traffic to OpenAI or ChatGPT. The mainswitchyardroute used an OpenAI-compatible LiteLLM gateway (claude-haiku-4-5). The gateway's own model IDs are replaced below with public model IDs.status=404 requested_model="codex-auto-review"declinedcodex_approve_allcodex_approve_all approved an action without a review,status=200200passthroughtoclaude-opus-4-8(gateway)status=200 requested_model="codex-auto-review" selected_model="claude-opus-4-8"200passthroughtogpt-5.6-luna(gateway)status=200 requested_model="codex-auto-review" selected_model="gpt-5.6-luna"200model_catalog_jsonoverrideswitchyard,status=200; nocodex-auto-reviewrequest200The Opus target needed
omit_body_fields = ["reasoning_effort"]; without it, the gateway returned 400. For the installer's setup, a local stub replaced the ChatGPT backend and recorded what Switchyard forwarded. The details below have both, plus the full server log and Codex events from the run without a reviewer route. I did not test how the real ChatGPT backend answers (that needs ChatGPT traffic) or an API-key login.codex_approve_all_route_returns_an_allow_verdictsends a request built like Codex's review request to/v1/responses, streamed and buffered, and checks that the reply text parses as{"outcome": "allow"}. The config has no targets, so the test fails if the route tries to call a model. It fails on the old code because the config does not load.Server log and Codex events without a reviewer route
Codex events, trimmed from
codex exec --json:{"type":"command_execution","command":"/bin/zsh -lc \"curl -sS -o /dev/null -w '%{http_code}' https://example.com\"","status":"declined"} {"type":"agent_message","text":"I encountered a system-level issue with the automatic approval review. The escalation request was rejected due to an infrastructure error rather than a policy decision. ..."}Installer setup: what Switchyard forwards to the ChatGPT backend
I replayed a captured Codex review request through the installer config, with a local stub in place of the ChatGPT backend. The stub recorded:
codex-auto-review, withstream: trueandstore: false, and a body identical to the one sentinput[0]as anadditional_toolsitem, noinstructions, andreasoning.effort: "low"x-openai-subagent: guardianandx-openai-internal-codex-responses-lite: trueauthorizationandchatgpt-account-idheadersOpus reviewer: 400 without
omit_body_fieldsOn the gateway I tested, the Opus reviewer first returned 400. Codex sends
reasoning.effort: "low"with every review request, and Switchyard passes that setting to the gateway asreasoning_effort. The gateway's 400 error says"thinking.type.enabled" is not supported for this model. Addingomit_body_fields = ["reasoning_effort"]to that target fixed it: the same request withoutreasoning_effortreturns 200.Menu bar prices and totals with the installer's
menubar.tomlThe menu bar hides a period's dollar figures (Today, This week) when any model seen in that period has no price. Without a
codex-auto-reviewprice, the first review in the installer's setup would hide that period's Saved row. The installer pricescodex-auto-reviewat thebaseline_modelrates (gpt-5.6-sol), because with a ChatGPT login Codex sends review requests there even without Switchyard. Each review then adds the same amount to the actual cost and to the baseline. The dollars saved stay the same, and the percentage drops a little because the baseline grows. A lower price would make reviews look like savings that routing did not produce.The docs now say which model ID needs a price in
menubar.toml: the target'sidwhen another model reviews, orcodex-auto-review(logged with zero tokens) forcodex_approve_all.A throwaway test loaded the installer's
menubar.toml, wrote routing-log lines, and printed the menu rows:codex_approve_allreplay on/v1/responsesConfig: only a
codex-auto-reviewroute withtype = "codex_approve_all"and an empty[targets]table. The request body is a captured Codex review request, sent once withstream: trueand once withstream: false, with the headersx-openai-subagent: guardianandx-openai-internal-codex-responses-lite: true.