`-c` overrides: dotted-path parser has no TOML quoted-key support, so `projects."/path".trust_level` is silently misapplied

Open 💬 2 comments Opened Jul 20, 2026 by jph00

What version of Codex CLI is running?

codex-cli 0.144.6

What subscription do you have?

ChatGPT (logged in via codex login; not relevant to this bug)

Which model were you using?

gpt-5.6-luna (not relevant: config parsing bug, model-independent)

What platform is your computer?

macOS (Darwin 25.5.0, Apple Silicon)

What terminal emulator and version are you using (if applicable)?

n/a (headless codex exec)

What issue are you seeing?

-c key=value overrides use a naive dotted-path split (str::split('.')) with no support for TOML quoted key segments. A key like:

-c 'projects."/tmp/proj".trust_level="trusted"'

is split on every . and the quote characters are kept as literal key text, so the override is stored under a key segment that includes the quotes ("/tmp/proj" rather than /tmp/proj). Nothing errors: the setting is simply never matched against the canonicalized project path, so the project stays untrusted and its .codex/rules execpolicy files silently do not load.

The workaround is to omit the quotes (-c projects./tmp/proj.trust_level=trusted), which works because the naive split then happens to produce the right segments. But that only works for paths containing no dots. A corollary of the same parser: any key whose segment legitimately contains a . (e.g. a project path like /Users/me/my.project) cannot be addressed from -c at all.

Relevant code: apply_toml_override in codex-rs/config/src/overrides.rs, which does path.split('.') with no quote handling.

What steps can reproduce the bug?

  1. Create a project dir /tmp/proj containing: a .git directory; an executable step.sh that appends a line to /tmp/proj/file.txt; and .codex/rules/allow.rules containing:

``python
prefix_rule(pattern = ["/tmp/proj/step.sh"], decision = "allow")
``

  1. From inside /tmp/proj, run:

``
codex exec --skip-git-repo-check -s read-only -c approval_policy=never \
-c 'projects."/tmp/proj".trust_level="trusted"' \
'Run exactly this command: /tmp/proj/step.sh'
`
The script runs inside the read-only sandbox and its write is denied: the trust override did not apply, so the project rules never loaded and the
allow` rule could not grant the sandbox bypass.

  1. Re-run with the unquoted spelling:

``
codex exec --skip-git-repo-check -s read-only -c approval_policy=never \
-c projects./tmp/proj.trust_level=trusted \
'Run exactly this command: /tmp/proj/step.sh'
``
Now the rules load, the allow rule matches, and the script's write succeeds.

codex execpolicy check --rules /tmp/proj/.codex/rules/allow.rules /tmp/proj/step.sh confirms the rule itself is fine ("decision":"allow"), isolating the failure to the -c key parsing.

What is the expected behavior?

Either parse dotted -c paths with TOML key semantics (so quoted segments work, matching how the same key must be written in config.toml), or reject keys containing quote characters with an error. The current behavior accepts the override and silently misapplies it, which is the worst of both: the user has written the spelling that works in config.toml, gets no diagnostic, and the resulting misconfiguration (rules not loading) is invisible until a downstream permission check behaves unexpectedly.

Additional information

This issue was authored by an AI assistant under the supervision of Jeremy Howard, who reviewed it.

View original on GitHub ↗

2 Comments

bbingz · 1 month ago

Analysis (community)

Thanks for the clear repro and code pointer — this looks solid from a static read of main (as of 2026-07-24). I did not re-run the full codex exec flow end-to-end, but the key-path behavior follows directly from the parser.

  • Environment (from issue): Codex CLI 0.144.6, macOS (Apple Silicon), headless codex exec
  • Repro status: Confirmed at the parser layer (source + segment-split check). Issue’s execpath repro (quoted trust override fails; unquoted workaround succeeds; execpolicy check isolates rules as fine) looks consistent with that root cause. I did not re-execute the sandbox/rules scenario myself.
  • Observed: -c 'projects."/tmp/proj".trust_level="trusted"' is accepted with no diagnostic, but trust/rules do not apply. Unquoted projects./tmp/proj.trust_level=trusted works for paths without ..
  • Expected: Same spelling as TOML quoted keys should either apply correctly, or fail loudly — not silently land under a wrong map key.

Segment split of the key side only (value-side TOML parse is fine):

| CLI key | Naive split('.') segments |
| --- | --- |
| projects."/tmp/proj".trust_level | projects, "/tmp/proj", trust_level |
| projects./Users/me/my.project.trust_level | projects, /Users/me/my, project, trust_level |

So quotes become part of the key text, and any real . inside a path segment is fatal under the same splitter.

Root-cause hypothesis

CLI -c keys stay as raw dotted strings through parse, then are walked with a naive splitter — never with TOML quoted-key segment rules.

  1. CliConfigOverrides::parse_overrides (codex-rs/utils/cli/src/config_override.rs) keeps the key string intact (only canonicalize_override_key for a legacy alias). The value is parsed as TOML; the key path is not segmented.
  2. build_cli_overrides_layer → apply_toml_override (codex-rs/config/src/overrides.rs) walks with path.split('.') and inserts each segment as a table key. Quotes are literal characters in the key.
  3. Trust lookup is exact (plus normalize), not quote-tolerant: project_config_for_lookup_key / project_trust_for_lookup_key (config_toml.rs / loader/mod.rs) look up the real path key. A stored key like "/tmp/proj" (with quote chars) never matches /tmp/proj, so the override “succeeds” and still has no effect on trust/rules.

That matches the silent misapply: wrong map key, no error, downstream trust miss.

Prior art already in-tree: parse_key_path in codex-rs/app-server/src/config_manager_service.rs already splits on . only outside double-quoted segments, strips quotes, honors escapes, and errors on empty/unterminated/invalid segments. CLI overrides look like they want the same rules, not a second ad-hoc dialect.

Related class (not a fix for this): #28654 (quoted keys for MCP names). Help text for -c currently documents bare dotted paths only, which steers people toward the TOML-like quoted form that then fails silently.

High-level fix outline (not a PR)

Minimal approach I’d consider, for maintainer alignment only:

  1. Shared dotted key-path parser — extract parse_key_path (or equivalent) into something reusable (codex-config / utils), matching app-server: split on . outside "…", strip quotes, honor escapes, error on empty/unterminated/invalid segments.
  2. Change apply_toml_override (or add apply_toml_override_segments) to walk Vec<String> segments instead of split('.').
  3. Optionally parse early in parse_overrides so bad keys fail at CLI with a clear message rather than at merge time.
  4. Prefer support over reject-only: accept projects."/path/with.dots".trust_level=trusted (issue preference); keep unquoted bare keys working for simple keys.
  5. Docs/help: note that segments containing . must be double-quoted, consistent with TOML.

Risks / edge cases

  • Unlikely behavior change if anyone relied on literal " characters inside keys.
  • Escape edge cases; stay consistent with app-server (double quotes only unless there is a reason to accept single quotes).
  • Optional follow-up: origin/fingerprint paths that join(".") may need re-quoting for segments that contain . so round-trips stay valid — can be a separate decision.

Alternatives considered

  • Reject any " in -c keys: smaller change, still blocks dotted path segments unless another escaping scheme is invented.
  • Special-case only projects.*: too narrow; same bug hits any table key with . or intentional quotes.

Non-goals (from this analysis)

  • No change to config.toml deserialization, trust lookup rules themselves, sandbox/execpolicy semantics, or app-server merge strategies beyond optional shared parser reuse.

Test ideas

Unit tests in codex-rs/config (overrides) and/or codex-rs/utils/cli that fail before / pass after:

  • (a) build_cli_overrides_layer([("projects.\"/tmp/proj\".trust_level", "trusted")]) must produce table key /tmp/proj, not "/tmp/proj".
  • (b) Dotted path segment: projects.\"/Users/me/my.project\".trust_level → single segment /Users/me/my.project.
  • (c) Bare simple keys still work: model, foo.bar.
  • (d) Errors: unterminated quote, empty segment, mid-segment quote.
  • (e) Optional integration: load_with_cli_overrides with projects.\"…\".trust_level and assert active project trust is Trusted (mirrors issue intent without full exec).

If a shared parser is extracted, mirror app-server escape cases there too.

Questions for maintainers

Happy to be corrected on any of these:

  1. Prefer extracting shared parse_key_path into config/utils vs a minimal local parser only in overrides.rs?
  2. Error only on invalid quoted paths, or also warn when a segment still contains literal " after parse (legacy broken spellings)?
  3. Double quotes only (match app-server), or also single-quoted segments?
  4. Should fingerprint/origin path joining re-quote segments containing . for valid round-trips, or leave that out of scope?
  5. Any intentional consumers of -c that already pass unquoted absolute paths with dots and would need migration messaging?

---

Happy to refine this analysis if anything above is off. If the team later wants an invited PR along these lines, I’d be glad to help — no rush and no expectation either way.

bbingz · 1 month ago

Building on the root-cause analysis above with the introducing-commit archaeology and a blast-radius check on --strict-config. Verified against main as of 5dd992a (2026-07-24).

Introducing archaeology. The naive path.split('.') walker in apply_toml_override is old. git log -S "path.split('.')" --all shows it originated in d60f350cf8 / #1137 ("feat: add support for -c/--config to override individual config items"), which introduced fn apply_toml_override with path.split('.') in codex-rs/core/src/config.rs; #7924 (92098d36e8, "clean config loading and config api") later moved it into config_loader/overrides.rs; #11389 (2026-02-11, "Extract codex-config from codex-core") extracted that into codex-rs/config/src/overrides.rs unchanged. git log -S "path.split('.')" -- codex-rs/config/src/overrides.rs returns only #11389 (path-scoped pickaxe). So this is a long-standing behavior (since #1137), not a 2026-07-20 regression.

Blast-radius extension - --strict-config does not catch this. I expected the strict validator to flag the silently-misapplied override, but it does not. validate_cli_overrides_strictly (codex-rs/config/src/loader/mod.rs:562-583) uses ignored_toml_value_field::<ConfigToml> (serde_ignored - rejects unknown fields anywhere under ConfigToml, including nested) plus unknown_feature_toml_value_field (unknown feature fields). But projects is a known open HashMap<String, ProjectConfig>, so a stored key like "/tmp/proj" (with literal quote characters) is a valid map entry, not ignored - strict validation returns Ok(()) and the broken override is accepted silently. The --strict-config flag is real and user-facing (codex-rs/tui/src/cli.rs:16-17, wired at loader/mod.rs:216), so even users who opt into strict validation get the silent failure - strengthening the issue's "worst of both" framing.

Test gap. codex-rs/config/src/overrides.rs has no #[cfg(test)] module, so apply_toml_override / build_cli_overrides_layer have no unit guardrail. The tests in codex-rs/utils/cli/src/config_override.rs (:104-170) cover only value-side parsing (parse_toml_value), the use_legacy_landlock alias, and prepend_root_overrides - none exercise dotted-key segmentation or quoted segments. For contrast, codex-rs/core/src/config/config_tests.rs:4192 uses a TOML-file quoted key [projects."/tmp/test"] that works via the TOML deserializer - consistent with the failure being isolated to the -c CLI splitter, not config.toml parsing. A regression test alongside any fix (quoted-segment segmentation matching parse_key_path in codex-rs/app-server/src/config_manager_service.rs:518) would close this.

Not fixed as of 5dd992a. overrides.rs is unchanged since #11389; config_override.rs has 3 later commits since #11389 (#13996, #20559, #34345) but none add key-segment tests; no commit references #34261.