diff --git a/headroom/cli/wrap.py b/headroom/cli/wrap.py index 4afea9af9..6e7d2c7db 100644 --- a/headroom/cli/wrap.py +++ b/headroom/cli/wrap.py @@ -1872,8 +1872,21 @@ def _codex_toml_value(value: Any) -> str: raise TypeError(f"unsupported Codex config override: {type(value).__name__}") +_CODEX_BARE_KEY_RE = re.compile(r"^[A-Za-z0-9_-]+$") + + def _codex_dotted_key(*parts: str) -> str: - return ".".join(json.dumps(part) for part in parts) + """Dotted ``--config`` key with segments bare wherever possible. + + Codex's override parser (observed on 0.144.x) matches dotted segments + literally and silently ignores quoted ones, so quoting every segment made + the whole override a no-op and traffic bypassed the proxy (#2358). Quote + only segments that are not valid TOML bare keys (e.g. a provider name + containing a dot), where bare emission would corrupt the path. + """ + return ".".join( + part if _CODEX_BARE_KEY_RE.fullmatch(part) else json.dumps(part) for part in parts + ) def _codex_session_launch_settings( diff --git a/tests/test_cli/test_wrap_codex.py b/tests/test_cli/test_wrap_codex.py index 0c86fd278..3835f98e8 100644 --- a/tests/test_cli/test_wrap_codex.py +++ b/tests/test_cli/test_wrap_codex.py @@ -1172,11 +1172,39 @@ def test_codex_session_launch_settings_preserve_custom_provider_identity( ) assert "model_provider=headroom" not in " ".join(args) - assert '"model_providers"."company"."base_url"="http://127.0.0.1:9898/v1"' in args + # Bare dotted keys — Codex (0.144.x) silently ignores quoted segments (#2358). + assert 'model_providers.company.base_url="http://127.0.0.1:9898/v1"' in args + assert "model_providers.company.supports_websockets=true" in args + assert ( + "model_providers.company.env_http_headers.X-Headroom-Base-Url" + '="HEADROOM_CODEX_UPSTREAM_BASE_URL"' + ) in args assert env[wrap_mod._UPSTREAM_BASE_URL_ENV_VAR] == "https://api.example.test/v1" assert config_file.read_text(encoding="utf-8") == original_config +def test_codex_dotted_key_emits_bare_segments_when_safe() -> None: + """#2358: quoted segments are silently ignored by Codex's --config parser.""" + assert ( + wrap_mod._codex_dotted_key("model_providers", "litellm_prod", "base_url") + == "model_providers.litellm_prod.base_url" + ) + # Hyphens are valid in bare keys (header names under env_http_headers). + assert ( + wrap_mod._codex_dotted_key("env_http_headers", "X-Headroom-Base-Url") + == "env_http_headers.X-Headroom-Base-Url" + ) + + +def test_codex_dotted_key_quotes_only_unsafe_segments() -> None: + # A provider name that would corrupt the dotted path if emitted bare keeps + # its quotes; every safe neighbor stays bare. + assert ( + wrap_mod._codex_dotted_key("model_providers", "my.provider", "base_url") + == 'model_providers."my.provider".base_url' + ) + + def test_wrap_codex_rejects_custom_provider_without_upstream_base_url( runner: CliRunner, monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: