`-c` overrides: dotted-path parser has no TOML quoted-key support, so `projects."/path".trust_level` is silently misapplied
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?
- Create a project dir
/tmp/projcontaining: a.gitdirectory; an executablestep.shthat appends a line to/tmp/proj/file.txt; and.codex/rules/allow.rulescontaining:
``python``
prefix_rule(pattern = ["/tmp/proj/step.sh"], decision = "allow")
- 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'
allow` rule could not grant the sandbox bypass.
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
- 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.
2 Comments
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 fullcodex execflow end-to-end, but the key-path behavior follows directly from the parser.0.144.6, macOS (Apple Silicon), headlesscodex execexecpolicy checkisolates rules as fine) looks consistent with that root cause. I did not re-execute the sandbox/rules scenario myself.-c 'projects."/tmp/proj".trust_level="trusted"'is accepted with no diagnostic, but trust/rules do not apply. Unquotedprojects./tmp/proj.trust_level=trustedworks for paths without..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
-ckeys stay as raw dotted strings through parse, then are walked with a naive splitter — never with TOML quoted-key segment rules.CliConfigOverrides::parse_overrides(codex-rs/utils/cli/src/config_override.rs) keeps the key string intact (onlycanonicalize_override_keyfor a legacy alias). The value is parsed as TOML; the key path is not segmented.build_cli_overrides_layer→apply_toml_override(codex-rs/config/src/overrides.rs) walks withpath.split('.')and inserts each segment as a table key. Quotes are literal characters in the key.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_pathincodex-rs/app-server/src/config_manager_service.rsalready 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
-ccurrently 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:
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.apply_toml_override(or addapply_toml_override_segments) to walkVec<String>segments instead ofsplit('.').parse_overridesso bad keys fail at CLI with a clear message rather than at merge time.projects."/path/with.dots".trust_level=trusted(issue preference); keep unquoted bare keys working for simple keys..must be double-quoted, consistent with TOML.Risks / edge cases
"characters inside keys.join(".")may need re-quoting for segments that contain.so round-trips stay valid — can be a separate decision.Alternatives considered
"in-ckeys: smaller change, still blocks dotted path segments unless another escaping scheme is invented.projects.*: too narrow; same bug hits any table key with.or intentional quotes.Non-goals (from this analysis)
config.tomldeserialization, 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/orcodex-rs/utils/clithat fail before / pass after:build_cli_overrides_layer([("projects.\"/tmp/proj\".trust_level", "trusted")])must produce table key/tmp/proj, not"/tmp/proj".projects.\"/Users/me/my.project\".trust_level→ single segment/Users/me/my.project.model,foo.bar.load_with_cli_overrideswithprojects.\"…\".trust_leveland assert active project trust isTrusted(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:
parse_key_pathinto config/utils vs a minimal local parser only inoverrides.rs?"after parse (legacy broken spellings)?.for valid round-trips, or leave that out of scope?-cthat 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.
Building on the root-cause analysis above with the introducing-commit archaeology and a blast-radius check on
--strict-config. Verified againstmainas of5dd992a(2026-07-24).Introducing archaeology. The naive
path.split('.')walker inapply_toml_overrideis old.git log -S "path.split('.')" --allshows it originated ind60f350cf8/ #1137 ("feat: add support for -c/--config to override individual config items"), which introducedfn apply_toml_overridewithpath.split('.')incodex-rs/core/src/config.rs; #7924 (92098d36e8, "clean config loading and config api") later moved it intoconfig_loader/overrides.rs; #11389 (2026-02-11, "Extractcodex-configfromcodex-core") extracted that intocodex-rs/config/src/overrides.rsunchanged.git log -S "path.split('.')" -- codex-rs/config/src/overrides.rsreturns only #11389 (path-scoped pickaxe). So this is a long-standing behavior (since #1137), not a 2026-07-20 regression.Blast-radius extension -
--strict-configdoes 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) usesignored_toml_value_field::<ConfigToml>(serde_ignored - rejects unknown fields anywhere underConfigToml, including nested) plusunknown_feature_toml_value_field(unknown feature fields). Butprojectsis a known openHashMap<String, ProjectConfig>, so a stored key like"/tmp/proj"(with literal quote characters) is a valid map entry, not ignored - strict validation returnsOk(())and the broken override is accepted silently. The--strict-configflag is real and user-facing (codex-rs/tui/src/cli.rs:16-17, wired atloader/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.rshas no#[cfg(test)]module, soapply_toml_override/build_cli_overrides_layerhave no unit guardrail. The tests incodex-rs/utils/cli/src/config_override.rs(:104-170) cover only value-side parsing (parse_toml_value), theuse_legacy_landlockalias, andprepend_root_overrides- none exercise dotted-key segmentation or quoted segments. For contrast,codex-rs/core/src/config/config_tests.rs:4192uses a TOML-file quoted key[projects."/tmp/test"]that works via the TOML deserializer - consistent with the failure being isolated to the-cCLI splitter, notconfig.tomlparsing. A regression test alongside any fix (quoted-segment segmentation matchingparse_key_pathincodex-rs/app-server/src/config_manager_service.rs:518) would close this.Not fixed as of
5dd992a.overrides.rsis unchanged since #11389;config_override.rshas 3 later commits since #11389 (#13996, #20559, #34345) but none add key-segment tests; no commit references #34261.