fix(codex): avoid duplicate headroom provider config (#1431)

## Description

Fixes #1425.

`headroom wrap codex` could leave `~/.codex/config.toml` invalid when
the user already had a `[model_providers.headroom]` table. The previous
duplicate-key handling covered top-level `model_provider` and
`openai_base_url`, but the provider table was still appended as a static
block. That could produce duplicate `env_http_headers` or duplicate
provider-table TOML errors before Codex started.

## Type of Change

- [x] Bug fix (non-breaking change fixes an issue)
- [ ] New feature (non-breaking change adds functionality)
- [ ] Breaking change (fix or feature would cause existing functionality
change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Added a Codex config cleanup helper that removes any pre-existing
`[model_providers.headroom]` table from the working copy before `wrap
codex` appends the managed Headroom provider block.
- Kept unwrap behavior backed by the existing pre-wrap snapshot, so a
custom prior `headroom` provider table is restored byte-for-byte on
`headroom unwrap codex`.
- Added regression tests for TOML validity, a single `env_http_headers`
mapping, one managed `[model_providers.headroom]` table, and unwrap
restoration.

## Testing

- [x] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added to cover the fix

### Test Output

```text
Docker: python:3.12-slim
Command: uv run --frozen --with pytest pytest tests/test_cli/test_wrap_codex.py
Result: 68 passed, 1 warning
```

## Real Behavior Proof

- Environment: disposable Docker container, `python:3.12-slim`, Linux,
Python 3.12.13.
- Exact command / steps: mounted the worktree into `/workspace`,
installed build tools inside the container, then ran `uv run --frozen
--with pytest pytest tests/test_cli/test_wrap_codex.py`.
- Observed result: all Codex wrap tests passed, including the new
regression where an existing `[model_providers.headroom]` table contains
`env_http_headers` before wrapping.
- Not tested: live interactive `headroom wrap codex` launch against a
real user Codex session.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review
This commit is contained in:
Rudimar Ronsoni 2026-06-30 20:42:48 +02:00 committed by GitHub
parent 15ac650d40
commit ddd4adf911
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 0 deletions

View file

@ -1350,6 +1350,20 @@ def _strip_codex_headroom_blocks(
_REDIRECTABLE_KEYS: tuple[str, ...] = ("model_provider", "openai_base_url")
def _strip_existing_codex_headroom_provider_table(content: str) -> str:
"""Remove a pre-existing ``[model_providers.headroom]`` table before wrap."""
if "[model_providers.headroom]" not in content:
return content
import re # local import to match surrounding helper convention
provider_table = re.compile(
r"(?ms)^[ \t]*\[model_providers\.headroom\][^\n]*\n.*?(?=^[ \t]*\[|\Z)"
)
content = provider_table.sub("", content)
return content.lstrip("\n").rstrip() + "\n" if content.strip() else ""
def _redirect_existing_top_level_keys(content: str, port: int) -> str:
"""Rewrite user-defined top-level keys so wrap does not create duplicates.
@ -1614,6 +1628,7 @@ def _inject_codex_provider_config(port: int) -> None:
# Remove any prior Headroom-managed blocks before re-injecting so
# the operation is idempotent and supports port changes.
content = _strip_codex_headroom_blocks(content)
content = _strip_existing_codex_headroom_provider_table(content)
# Bare top-level keys must precede any [section] in TOML, and
# TOML rejects duplicate top-level keys. Rewrite any existing

View file

@ -735,6 +735,61 @@ class TestInjectAvoidsDuplicateTopLevelKeys:
assert 'openai_base_url = "http://127.0.0.1:8787/v1"' in content
assert "[model_providers.headroom]" in content
def test_inject_replaces_existing_headroom_provider_table(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Existing headroom provider table must not create duplicate TOML keys."""
import tomllib # Python 3.11+ stdlib
_set_test_home(monkeypatch, tmp_path)
config_dir = tmp_path / ".codex"
config_dir.mkdir()
config_file = config_dir / "config.toml"
config_file.write_text(
"[model_providers.headroom]\n"
'name = "Existing custom headroom"\n'
'base_url = "http://example.invalid/v1"\n'
"supports_websockets = true\n"
'env_http_headers = { "X-Headroom-Project" = "HEADROOM_PROJECT" }\n'
"\n"
"[profiles.default]\n"
'model = "gpt-5"\n'
)
wrap_mod._inject_codex_provider_config(8787)
content = config_file.read_text()
tomllib.loads(content)
assert content.count("[model_providers.headroom]") == 1
assert content.count("env_http_headers") == 1
assert 'base_url = "http://127.0.0.1:8787/v1"' in content
assert 'env_http_headers = { "X-Headroom-Project" = "HEADROOM_PROJECT" }' in content
assert "[profiles.default]" in content
assert 'model = "gpt-5"' in content
def test_unwrap_restores_prior_headroom_provider_table(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Pre-wrap headroom provider table is restored from snapshot."""
_set_test_home(monkeypatch, tmp_path)
config_dir = tmp_path / ".codex"
config_dir.mkdir()
config_file = config_dir / "config.toml"
original = (
"[model_providers.headroom]\n"
'name = "Existing custom headroom"\n'
'base_url = "http://example.invalid/v1"\n'
"supports_websockets = true\n"
'env_http_headers = { "X-Headroom-Project" = "HEADROOM_PROJECT" }\n'
)
config_file.write_text(original)
wrap_mod._inject_codex_provider_config(8787)
status, _ = wrap_mod._restore_codex_provider_config()
assert status == "restored"
assert config_file.read_text() == original
def test_unwrap_restores_prior_model_provider_after_rewrite(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None: