Skip to content

fix(codex): prevent duplicate [projects] tables from breaking config.toml load#7926

Open
Cainiaooo wants to merge 1 commit into
stablyai:mainfrom
Cainiaooo:fix/codex-config-toml-duplicate-project-key
Open

fix(codex): prevent duplicate [projects] tables from breaking config.toml load#7926
Cainiaooo wants to merge 1 commit into
stablyai:mainfrom
Cainiaooo:fix/codex-config-toml-duplicate-project-key

Conversation

@Cainiaooo

Copy link
Copy Markdown

Summary

On Windows, Codex launched through Orca could crash at config load with failed to load configuration: ...\config.toml:NNN: duplicate key. The managed config.toml accumulated two [projects....] tables that decode to the same TOML key — a basic string (double quotes, escaped backslashes) written by Orca and a literal string (single quotes, raw backslashes) written by the Codex CLI. TOML compares keys by decoded value, so the two collide and Codex refuses to load the file. It recurred on every launch (the config mirror re-emitted the collision), so deleting the offending line never stuck.

This PR makes project-trust reads/writes compare [projects....] headers by their decoded, normalized key across both the trust writer and the config mirror, and collapses any pre-existing decoded-duplicate to a single table.

Root cause / fix

  • upsertProjectTrustLevelInContent matched only double-quoted (basic) headers, so an existing literal header was not found and a duplicate basic table was appended. It now scans by decoded + normalized key and collapses all duplicate matches into one table.
  • The config mirror deduped project sections by raw header text, so basic / literal / slash / drive-case variants of one path both survived the merge. It now dedupes by decoded key, including system-side duplicates in both the merge and fresh-mirror paths.
  • New shared helper codex-project-trust-key.ts: Windows-style paths fold separators and drive-letter case; POSIX paths stay case-sensitive (SSH/WSL-safe). hooks.state keys are deliberately left untouched — their slash variants are genuinely distinct keys.

Screenshots

No visual change (config-file handling only).

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions

Note: the pnpm suite was not run in my environment (dependencies not installed there). I verified the changed modules end-to-end by driving the real code (upsertProjectTrustLevelInContent and syncSystemConfigIntoManagedCodexHome) against temporary configs — all new and regression scenarios pass — and node config/scripts/check-max-lines-ratchet.mjs reports no new bypasses. New tests cover: literal↔basic match, slash/drive-case drift, POSIX case sensitivity, pre-existing duplicate collapse, system-side dedup (merge + fresh mirror), and a hooks.state no-dedup guard. Please rely on CI for the full pnpm gate.

AI Review Report

Reviewed with Codex over two rounds. Round 1 flagged two P2 robustness gaps: (1) the upsert updated only the first matching table and left a pre-existing decoded-duplicate behind; (2) the mirror could still emit a system-side basic/literal duplicate when the runtime had no matching section (both the merge and fresh-mirror paths). Both were fixed — collapse-all in the upsert, and system-side dedup in stripRuntimeOwnedTomlSections — and re-review found no discrete correctness issues.

Cross-platform: this is Windows-focused; macOS/Linux/POSIX case sensitivity is explicitly preserved (POSIX paths are never folded), and the SSH/WSL case (POSIX paths under a Windows host, already-canonical remote paths) is handled by content-based normalization rather than process.platform. No keyboard shortcuts, labels, or shell behavior are touched; paths flow through decode/normalize helpers rather than raw separators.

Security Audit

The change only reads and rewrites ~/.codex/config.toml and Orca's managed runtime config.toml. No command execution, network, auth, secrets, or IPC surface is touched. Writes continue to use the existing atomic write + .bak rotation, and byte-preservation of unrelated config is retained (edits stay scoped to [projects....] sections). Input is untrusted TOML from disk; parsing is bounded single-line string decoding with no eval or regex-DoS risk.

Notes

  • The reported crash affects Orca-launched Codex (managed CODEX_HOME); the runtime config self-heals on the next launch once this ships.
  • A pre-existing duplicate in the global ~/.codex/config.toml (from before this fix) is collapsed the next time Orca marks that project trusted, but is not otherwise migrated.
  • No dependency or schema changes.

🤖 Generated with Claude Code

…toml load

Orca-launched Codex could fail at config load with `duplicate key` because the
managed config.toml ended up with two [projects....] tables that decode to the
same TOML key -- one basic string (written by Orca) and one literal string
(written by the Codex CLI). The error recurred on every launch because the
config mirror re-emitted the collision, so manual edits never stuck.

Two format-agnosticity gaps caused and preserved this on Windows:

- upsertProjectTrustLevelInContent only matched double-quoted (basic) project
  headers, so an existing single-quoted (literal) header was not found and a
  duplicate basic table was appended. It now matches by decoded + normalized
  key and collapses any pre-existing decoded-duplicate to a single table.

- The config mirror deduped project sections by raw header text, so basic vs
  literal / slash / drive-case variants of one path both survived the merge.
  It now dedupes by decoded key and also collapses system-side duplicates in
  both the merge and fresh-mirror paths.

Project keys are compared via a shared decode/normalize helper: Windows-style
paths fold separators and drive-letter case, while POSIX paths stay
case-sensitive. hooks.state keys are untouched -- their slash variants are
intentionally distinct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec0324a7-f450-40ab-80d1-c0d3bb47eafe

📥 Commits

Reviewing files that changed from the base of the PR and between 06afbc4 and 42b9b70.

📒 Files selected for processing (6)
  • src/main/codex/codex-config-mirror.test.ts
  • src/main/codex/codex-config-mirror.ts
  • src/main/codex/codex-project-trust-key.test.ts
  • src/main/codex/codex-project-trust-key.ts
  • src/main/codex/config-toml-trust.test.ts
  • src/main/codex/config-toml-trust.ts

📝 Walkthrough

Walkthrough

This PR adds a new module, codex-project-trust-key.ts, that decodes and normalizes TOML [projects...] table header paths (handling basic/literal quote styles, Windows drive-letter casing, and slash direction) to produce stable comparison keys. codex-config-mirror.ts is updated to use this comparison key for deduplicating runtime/system project sections during config merging, with untrusted entries overriding trusted ones on collision. config-toml-trust.ts replaces its previous regex-based header matching with a structural line-scanner that locates and collapses duplicate project tables using the same comparison key. Corresponding unit tests were added for all three areas.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the config.toml duplicate-project-table fix and matches the main change.
Description check ✅ Passed The description includes all required sections and gives enough detail on summary, testing, review, security, and notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants