fix(codex): preserve literal Windows TOML path keys#2100
Conversation
There was a problem hiding this comment.
Pull request overview
This PR switches Codex TOML parsing/serialization to tomlkit to preserve literal-quoted Windows path keys during MCP server updates and during stale-server cleanup, addressing the data-loss / parse-rejection behavior reported in #2075.
Changes:
- Use
tomlkitfor Codexconfig.tomlreads/writes to preserve literal key spelling across updates. - Update stale MCP cleanup for TOML configs to round-trip with
tomlkitand skip non-tablemcp_servers. - Add unit regression tests for Windows-style literal-quoted TOML keys and stale cleanup preservation.
- Add
tomlkit>=0.13to dependencies (pyproject.toml,uv.lock).
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/adapters/client/codex.py |
Switch Codex config TOML read/write to tomlkit for round-trip preservation. |
src/apm_cli/integration/mcp_integrator.py |
Use tomlkit for TOML stale-server cleanup with a mapping guard. |
tests/unit/test_codex_adapter_phase3.py |
Add regression tests covering Windows literal-quoted path keys and repeated writes. |
tests/unit/test_mcp_integrator_remove_stale.py |
Add tests ensuring stale cleanup preserves unrelated Windows literal-key content and skips malformed mcp_servers. |
pyproject.toml |
Add direct dependency on tomlkit>=0.13. |
uv.lock |
Lockfile update to include tomlkit. |
Review details
- Files reviewed: 5/6 changed files
- Comments generated: 3
- Review effort level: Low
|
|
||
| with open(config_path, "w", encoding="utf-8") as f: | ||
| toml.dump(current_config, f) | ||
| config_path.write_text(tomlkit.dumps(current_config), encoding="utf-8") |
| with open(config_path, encoding="utf-8") as config_file: | ||
| return tomlkit.load(config_file) | ||
| except ParseError as exc: |
| except (OSError, ParseError): | ||
| _log.debug("Failed to clean stale MCP servers from %s", label, exc_info=True) |
Use tomlkit for Codex config updates and stale-server cleanup so literal-quoted Windows paths round-trip without parser rejection or backslash corruption. Add cross-platform regression and mutation-break coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6e6d9c9 to
8d052d6
Compare
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 2 | 1 | tomlkit is sound; document the round-trip document contract. |
| CLI Logging Expert | 0 | 0 | 0 | No logging regressions. |
| DevX UX Expert | 0 | 2 | 1 | Add public install/cleanup flow coverage. |
| Supply Chain Security Expert | 0 | 1 | 2 | Config integrity improves; consolidate parsers separately. |
| OSS Growth Hacker | 0 | 1 | 1 | Record the Windows data-integrity fix in CHANGELOG. |
| Doc Writer | 0 | 0 | 1 | No Starlight drift; CHANGELOG needs the fix entry. |
| Test Coverage Expert | 0 | 1 | 1 | Helper tests pass; public orchestration regression trap is missing. |
B = blocking-severity findings, R = recommended, N = nits. Counts are signal strength, not gates. The maintainer ships.
Top 5 follow-ups
- [Test Coverage Expert] (blocking-severity) Add a real-file integration test through
MCPIntegrator.remove_stale()using Windows literal path keys, then prove the regression trap with mutation-break. - [DevX UX Expert] (blocking-severity) Use
write_text_lfin the Codex adapter so tomlkit output is written byte-for-byte on Windows. - [DevX UX Expert] Catch
UnicodeDecodeErrorat both Codex TOML read sites to retain safe no-write behavior. - [Python Architect] Document that an existing valid config returns a round-trip-preserving
TOMLDocument. - [OSS Growth Hacker] Add the [BUG] Windows: apm mishandles backslashes in Codex config.toml literal-quoted keys — parser rejects valid TOML, writer corrupts [projects.*] keys (data loss) #2075 fix under CHANGELOG
[Unreleased].
Recommendation
Fold the five in-scope items, run the mutation-break gate and full lint contract, then re-run this panel. Consolidating unrelated legacy toml consumers is outside this PR's scope.
Full per-persona findings
- Python Architect: clarify the
TOMLDocumentcontract; track unrelated parser consolidation separately. - CLI Logging Expert: no findings.
- DevX UX Expert: missing public orchestration test; deterministic newline and failure behavior need completion.
- Supply Chain Security Expert: dependency is locked and attributed; dual-parser consolidation belongs in a follow-up.
- OSS Growth Hacker and Doc Writer: add a concise CHANGELOG entry.
- Test Coverage Expert: four real-file tests pass, but the public
remove_staleflow lacks the Windows fixture. - Auth Expert: inactive; no auth surface touched.
- Performance Expert: inactive; no performance surface touched.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Add an empirical Codex install regression trap and fold panel and Copilot findings for LF writes, decode safety, contract documentation, and release notes. Mutation-breaking the round-trip load makes the integration test fail as expected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the atomic LF-normalized writer, catch all tomlkit failures at safe no-write boundaries, clarify decode warnings, and make the release note searchable. Addresses final panel follow-ups. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise Windows literal-key preservation through public stale cleanup, make cleanup writes atomic, assert actionable warnings, and remove redundant post-write chmod. Mutation-breaking the Codex dispatch makes the new test fail as expected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 0 | 0 | The round-trip and atomic-write design is cohesive and appropriately bounded. |
| CLI Logging Expert | 0 | 0 | 0 | Failure warnings are actionable and preserve safe no-write behavior. |
| DevX UX Expert | 0 | 0 | 0 | Codex install and cleanup preserve user-managed configuration predictably. |
| Supply Chain Security Expert | 0 | 0 | 0 | Atomic mode-0600 writes address configuration integrity and privacy. |
| OSS Growth Hacker | 0 | 0 | 0 | The Windows compatibility fix removes a concrete adoption obstacle. |
| Doc Writer | 0 | 0 | 0 | The CHANGELOG entry accurately captures the user-visible fix. |
| Test Coverage Expert | 0 | 0 | 0 | Public install and cleanup paths, preservation, errors, and mutation breaks are covered. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Recommendation
Merge immediately. CI is green at SHA 2abbeb86e13cca80f89758feccf054215983d1be (run 29064448578), all 300 relevant tests pass, both mutation-break tests confirm the production guards are load-bearing, all active panelists returned zero findings, and the CHANGELOG entry is in place. No follow-ups required.
Full per-persona findings
Python Architect
No findings.
CLI Logging Expert
No findings.
DevX UX Expert
No findings.
Supply Chain Security Expert
No findings.
OSS Growth Hacker
No findings.
Auth Expert -- inactive
The PR does not change authentication behavior, token management, credential resolution, or remote-host authorization.
Doc Writer
No findings.
Test Coverage Expert
No findings.
Performance Expert -- inactive
The PR does not change dependency transport, caching, materialization, parallelism, or an install/update performance hot path.
This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.
fix(codex): preserve literal Windows TOML path keys
TL;DR
Codex configuration now uses
tomlkitfor round-trip-safe reads and writes. Valid literal-quoted Windows path keys are accepted and preserved while APM adds or removes MCP servers, eliminating the parser rejection and backslash corruption reported in #2075.Important
Closes #2075. This changes only Codex TOML handling; no CLI contract or documentation changes are required.
Problem (WHY)
tomlparser rejected valid Codex keys such as'C:\Users\me\Documents\Playground', soapm install --global --only mcpskipped the Codex write.The regression tests make the behavior deterministic: "Grounding outputs in deterministic tool execution transforms probabilistic generation into verifiable action."
Approach (WHAT)
tomlkitso literal-key spelling and unrelated formatting survive updates.Implementation (HOW)
src/apm_cli/adapters/client/codex.pytomlloading/dumping withtomlkitround-trip documents while retaining parse-failure data-loss protection.src/apm_cli/integration/mcp_integrator.pymcp_serversuntouched.tests/unit/test_codex_adapter_phase3.pytests/unit/test_mcp_integrator_remove_stale.pypyproject.toml,uv.locktomlkit>=0.13as the direct round-trip TOML dependency.scripts/notice-metadata.yaml,NOTICEDiagrams
Legend: Only the
mcp_serverstable is changed; the dashed stages are the new round-trip-safe path.flowchart LR subgraph Read[Read] C[Codex config.toml] P[tomlkit parse] end subgraph Change[Change] M[Update mcp_servers only] end subgraph Write[Write] D[tomlkit dumps] O[Codex config.toml] end C --> P P --> M M --> D D --> O classDef new stroke-dasharray: 5 5; class P,M,D new;Trade-offs
tomlkit; rejected regex or text splicing because nested tables, quoted keys, and comments make partial TOML editing unsafe.tomldependency for existing consumers. This PR moves only Codex production paths; removing or migrating unrelated test and integration consumers would broaden scope.mcp_serversvalue is left byte-for-byte unchanged instead of risking user-config data loss.Benefits
\Uparse successfully on every supported host OS.[projects.'c:\...']and per-path preference text unchanged.Validation
Regression and relevant suites
uv run --extra dev pytest tests/unit/test_codex_adapter_phase3.py tests/unit/test_codex_adapter_compatibility.py tests/unit/test_mcp_integrator_remove_stale.py tests/unit/integration/test_mcp_integrator.py -qMutation-break proof using the previous
tomlread/write path:Full CI lint mirror
Scenario Evidence
tests/unit/test_codex_adapter_phase3.py::TestGetCurrentConfig::test_parses_windows_path_in_literal_quoted_keytests/unit/test_codex_adapter_phase3.py::TestUpdateConfig::test_update_preserves_windows_literal_quoted_path_keys(regression-trap for #2075)tests/unit/test_mcp_integrator_remove_stale.py::TestCleanCodexToml::test_preserves_windows_literal_keys_while_removing_stale_server(regression-trap for #2075)mcp_serversvalue untouched instead of corrupting the config.tests/unit/test_mcp_integrator_remove_stale.py::TestCleanCodexToml::test_skips_non_table_mcp_servers_without_rewritingHow to test
[desktop.open-in-target-preferences.perPath];get_current_config()should parse it without warning.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com