fix(codex): write a config the client can actually start with - #41
Merged
Conversation
added 2 commits
August 9, 2026 09:29
…ting the chosen model Audited the Codex provider config against the published reference and the client's own request builder. The wire protocol was already correct -- `wire_api = "responses"`, `base_url` ending in `/v1`, and a verbatim SSE passthrough that produces the full canonical event sequence including `response.function_call_arguments.delta`. The defects were all on the config surface. `model_context_window` was plumbed through `configure_codex` and unit tested, but both call sites passed `None`, so it was never written. Codex then budgets context from its built-in table, which is sized for OpenAI's public limits; Copilot serves the same slugs with different windows (1,050,000 for gpt-5.5, 264,000 for gpt-5-mini), so the client compacted at the wrong point in both directions. The value is now read from the live catalog for whichever model ends up selected -- looking it up for the recommended default instead would hand a 1M window to a config pinned to a 264K model. The key stays unwritten when the catalog is unreachable, and the headless path reads a token already on disk rather than starting a device flow nobody is watching. `model` was overwritten unconditionally, so running setup replaced a deliberate choice with the recommended default. It is now only written when absent. `model_provider` still moves, since repointing it is the entire purpose of the flag. The provider block named no API key. A proxy configured with `api_key` was therefore unreachable from Codex with no config path to fix it. It now gets `env_key = "GHC_PROXY_API_KEY"` -- the same variable the proxy reads -- but only when a key is actually set, because Codex fails a turn when a provider names an environment variable that is missing. The setup guide printed the default model name even when it had kept a different one, so `configure_codex` now returns the model it settled on. No change to `service_tier`. It looked like an unconditional override worth gating, but probing Copilot showed every value -- `priority`, `auto`, `default`, `flex` -- answers 400 "service_tier is not supported", while explicit null and omission both succeed. The existing nulling is load-bearing; it is now commented so the next reader does not undo it. Fast mode silently does nothing as a result, which is documented.
…n env var
Codex only reads provider credentials from an environment variable named by
env_key, and it fails a turn when that variable is unset. The proxy's api_key
can come from config.yaml, in which case nothing is ever exported and the
generated config broke Codex on startup.
Write http_headers = { Authorization = "Bearer <key>" } instead. The value
only opens the local proxy -- the credential that reaches GitHub is the Copilot
token the proxy holds -- so there is no reason to make it depend on the shell.
Merged
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.
Audited the Codex integration against the config reference and the client's own request builder.
The protocol was already correct
Worth stating first, since it's the part that could have been badly wrong.
/v1/responsesis a verbatim byte passthrough to Copilot's native endpoint, and sending Codex's exact payload shape (store:false,stream:true,include:["reasoning.encrypted_content"],prompt_cache_key,text,reasoning,tool_choice,parallel_tool_calls) produced the full canonical sequence:Tool calling works too — 11×
response.function_call_arguments.delta,.done,function_callitems, with thetype:"custom"apply_patchtool accepted.wire_api = "responses"matches the reference (it's the only supported value), andbase_urlcorrectly ends in/v1because Codex does not append a version.The defects were all on the config surface.
model_context_windowwas never writtenThe parameter was plumbed through
configure_codexand unit tested withSome(272000)— but both call sites passedNone.Codex budgets context from its own built-in table, sized for OpenAI's public limits. Copilot serves the same slugs with different windows:
gpt-5.5,gpt-5.6-solgpt-5.3-codexgpt-5-miniSo the client compacted at the wrong point in both directions. It's now read from the live catalog for whichever model ends up selected — looking it up for the recommended default would hand a 1M window to a config pinned to a 264K model. Verified:
The key stays unwritten when the catalog is unreachable, and the headless path uses a token already on disk rather than starting a device flow nobody is watching.
modelwas overwritten unconditionallyRunning setup replaced a deliberate choice with the recommended default. Now only written when absent:
The setup guide also printed the default model name even when it had kept a different one, so
configure_codexnow returns the model it settled on.The provider block named no API key
A proxy configured with
api_keywas unreachable from Codex with no config path to fix it. It now getsenv_key = "GHC_PROXY_API_KEY"— the same variable the proxy reads, so one exported value serves both sides — but only when a key is set, because Codex fails a turn when a provider names an environment variable that is missing.service_tier: no change, and that's the findingThis looked like an unconditional override worth gating. Probing Copilot says otherwise:
"priority"/"auto"/"default"/"flex"service_tier is not supportednullThe existing nulling is load-bearing. Gating it would have been a regression, so it stays — now with a comment recording the probe so the next reader doesn't undo it. Codex's Fast mode silently does nothing as a consequence, which is documented.
Also verified, no action
Copilot returns no reasoning items or
encrypted_contentdespite the request asking for them. That's upstream, not the proxy — passthrough is byte-verbatim. Codex tolerates it; reasoning continuity across turns is simply unavailable.The WebSocket transport stays unadvertised. The proxy implements
ws:/responses, but Codex's WS path is a much larger contract — v2response.createwithOpenAI-Beta: responses_websockets=2026-02-06,previous_response_id, incremental input deltas andx-codex-turn-statesticky routing.169 tests pass;
cargo fmt --all --checkandcargo clippy --all-targets -- -D warningsare clean.