Skip to content

fix(codex): recognize quoted projects keys in config.toml trust headers - #22605

Closed
leilei3167 wants to merge 3 commits into
stablyai:mainfrom
leilei3167:fix/issue-22592-quoted-projects-header
Closed

leilei3167 wants to merge 3 commits into
stablyai:mainfrom
leilei3167:fix/issue-22592-quoted-projects-header

Conversation

@leilei3167

Copy link
Copy Markdown
Contributor

ELI5

When Orca marks a worktree as trusted for Codex, it updates ~/.codex/config.toml. Codex sometimes writes the project table with quotes around the word projects (["projects"."/path"]). Orca only looked for the unquoted form, thought the project was missing, and appended a second table. TOML forbids that, so every later codex start failed with a duplicate-key error. This change teaches the header matcher to recognize both spellings so Orca updates the existing block instead of appending.

What Changed

  • parseProjectTomlHeaderPath now accepts bare projects, basic-quoted "projects", and literal-quoted 'projects' table keys before the path segment.
  • Path matching still goes through the existing TOML string decoder, so escaped characters in the path compare correctly.
  • Added regression coverage for both quoted-key spellings and for a path that needs basic-string unescaping.
  • Existing bare-header and literal-path Windows/WSL cases are unchanged.

Why

The locator returning “not found” was indistinguishable from “matcher too narrow,” so a formatting difference that Codex already emits turned into a broken config. Accepting the quoted key forms (same approach as recognizing both quote styles on the path) is the smallest fix that stops the append. Alternatives considered: rewriting every quoted header to the bare form on read (more invasive, rewrites user files unnecessarily), or only detecting duplicates after write without fixing the miss (still leaves trust updates broken until a second pass).

Linked Issue

Fixes #22592

Visual Proof

N/A — this is TOML header matching in the Codex trust upsert path; there is no UI or interaction change.

Testing

  • Automated tests added/updated, or explained why not below
  • I manually tested these changes locally

Regression: pnpm exec vitest run --config config/vitest.config.ts src/main/codex/config-toml-trust-project.test.ts

  • Before the matcher change: the three new quoted-key cases failed (existing ["projects"."…"] / ['projects'.'…'] blocks kept untrusted and a second [projects."…"] block was appended).
  • After: those three cases pass; full file 29/29 passed.
  • Platforms: logic is pure string parsing (macOS / Linux / Windows / SSH path values already covered by existing suite); no Electron UI run for this PR.

AI Disclosure

Review

Self-review notes:

  • Cross-platform: header forms are TOML grammar, not OS-specific; Windows/WSL path decoding paths unchanged.
  • SSH/remote/local: upsert still uses the same content helpers; only the header prefix match widened.
  • Agent/integration: Codex-specific config.toml; other agents untouched.
  • Performance: one optional alternation in an existing per-line regex; no new I/O.
  • Security: no credential or shell-path execution changes; still only rewrites trust_level inside matched project tables.
  • UI: none.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

No security, cross-platform, Remote SSH, mobile, backwards-compat, or performance concerns beyond the notes above. Bare [projects."…"] headers continue to match.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Codex writes ["projects"."/path"] tables; the matcher only accepted bare
projects., so upsert appended a duplicate and broke Codex startup.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: ac21bc47-b067-49f6-a428-55508fb20be5

📥 Commits

Reviewing files that changed from the base of the PR and between be61fa8 and 2a05bf7.

📒 Files selected for processing (1)
  • src/main/codex/codex-config-mirror.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The project header parser now recognizes bare, double-quoted, and single-quoted projects keys. Trust-level updates and reads also recognize quoted trust_level keys. Tests verify in-place updates, preservation of notes, and prevention of duplicate project tables and trust assignments. A mirror test verifies deduplication between quoted and bare project headers.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 2a05b

This change recognizes quoted Codex project and trust keys, preventing duplicate TOML entries for the covered forms. No actionable merge risk remains in the reviewed change.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: support for quoted projects keys in Codex trust headers.
Description check ✅ Passed The description covers the required sections, explains the user impact and implementation, links issue #22592, documents that no visual proof applies, lists regression testing and platform considerati…
Linked Issues check ✅ Passed Issue #22592 requires recognition of bare, basic-quoted, and literal-quoted projects keys. parseProjectTomlHeaderPath now matches all three forms. The upsert tests verify in-place trust updates, p…
Out of Scope Changes check ✅ Passed The parser, trust-level matching, runtime trust reading, and regression tests directly support issue #22592. The changes prevent duplicate project tables and duplicate trust assignments for quoted Cod…
  • Fix all pre-merge checks with AI

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.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The header matcher change itself is correct, but it may not fully resolve #22592. The reported Codex block quotes the trust key too ("trust_level" = "trusted"), and the trust-line matchers still require a bare trust_level. If that form is real, Orca now finds the header and then inserts a second trust_level, trading a duplicate table for a duplicate key. Details and the open question are inline.

Reviewed changes

  • Widened project-header matcher — parseProjectTomlHeaderPath now accepts projects, "projects", and 'projects' before the path segment, reusing the existing TOML string decoder for the path.
  • Regression tests — basic-quoted key, literal-quoted key, and basic-quoted key whose path needs \" unescaping; each asserts the existing block is updated in place rather than duplicated.
  • Reach of the parser — the same matcher feeds isRuntimeProjectTomlSection / getTomlSectionHeaderKey in config-toml-runtime-owned-sections.ts, so quoted project headers are now recognized in the mirror path as well.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/codex/config-toml-syntax.ts
Codex can write "trust_level" under a quoted projects header (stablyai#22592).
Widen the trust key matcher (and getProjectTrustLevel) so upsert replaces
the existing line instead of inserting a duplicate bare key.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review (be61fa8): compiling the quoted-key matchers for the trust value and their regression coverage.

  • Quoted trust_level in the upsert matcher — trustLevelPattern now accepts trust_level, "trust_level", and 'trust_level', so a block found via the widened header matcher has its existing key replaced instead of a second bare line inserted.
  • Quoted trust_level in mirror reads — getProjectTrustLevel was widened identically, so dedupe and revocation-preference read a quoted level rather than null.
  • Regression coverage — five new cases: quoted project key (basic + literal), quoted key whose path needs \" unescaping, and quoted trust_level (basic, matching the #22592 bytes, + literal). Each asserts a single trust key and no appended table/key.

Both production matchers that read trust_level are covered; the new files pass locally (config-toml-trust-project.test.ts 31/31, codex-config-mirror.test.ts 34/34), and the quoted-trust_level case genuinely fails against the header-only change.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 39916d75-6810-4364-a5b9-2f7ddbcdeb56

📥 Commits

Reviewing files that changed from the base of the PR and between f96fde9 and be61fa8.

📒 Files selected for processing (3)
  • src/main/codex/config-toml-project-trust.ts
  • src/main/codex/config-toml-runtime-owned-sections.ts
  • src/main/codex/config-toml-trust-project.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +140 to +142
// Why: same quoted-key forms as upsert (#22592); otherwise mirror/dedupe reads null.
const match =
/^[ \t]*trust_level[ \t]*=[ \t]*(?:"(trusted|untrusted)"|'(trusted|untrusted)')[ \t\r]*(?:#.*)?$/m.exec(
/^[ \t]*(?:trust_level|"trust_level"|'trust_level')[ \t]*=[ \t]*(?:"(trusted|untrusted)"|'(trusted|untrusted)')[ \t\r]*(?:#.*)?$/m.exec(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

git diff b4d732685cc9722ce9519c5031da87a4f18a4dc4 be61fa8beaa110b3720fe1e3898463be95d92abd -- src/main/codex/config-toml-runtime-owned-sections.ts src/main/codex/config-toml-project-trust.ts src/main/codex/config-toml-trust-project.test.ts
sed -n '110,165p' src/main/codex/config-toml-runtime-owned-sections.ts
rg -n 'getProjectTrustLevel|trust_level.*trusted|trusted.*trust_level' src/main/codex --glob '*test*' --glob '*.ts'

Repository: stablyai/orca

Length of output: 20790


🏁 Script executed:

cat -n src/main/codex/config-toml-runtime-owned-sections.ts | sed -n '1,155p'
cat -n src/main/codex/codex-config-mirror.ts | sed -n '225,285p'
cat -n src/main/codex/codex-config-mirror.test.ts | sed -n '290,455p'
cat -n src/main/codex/codex-config-mirror.test.ts | sed -n '590,675p'
cat -n src/main/codex/config-toml-trust-project.test.ts | sed -n '175,225p'
rg -n 'extractOrdinaryCodexSettings|deduplicateProjectTomlSections|mirror.*Codex|sync.*Codex|runtimeConfig' src/main/codex/codex-config-mirror.ts src/main/codex/codex-config-mirror.test.ts src/main/codex/config-toml-runtime-owned-sections.ts

Repository: stablyai/orca

Length of output: 41698


Cover quoted keys in the mirror revocation test.

The quoted-key cases in config-toml-trust-project.test.ts exercise the upsert writer, not getProjectTrustLevel through the mirror. The mirror revocation test uses a bare key. If the reader loses its quoted-key alternatives, the system revocation can be dropped when a matching runtime project exists, leaving the runtime’s trusted entry in place. Parameterize the mirror test over both quoted key forms.

Suggested fix
-  it('applies a CRLF system revocation to an LF runtime project', () => {
-    mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
-    writeFileSync(
-      getRuntimeConfigPath(),
-      ["[projects.'c:\\repo']", 'trust_level = "trusted"', ''].join('\n'),
-      'utf-8'
-    )
-    writeFileSync(
-      getSystemConfigPath(),
-      ['[projects."C:/repo"]', 'trust_level = "untrusted"', ''].join('\r\n'),
-      'utf-8'
-    )
-
-    syncSystemConfigIntoManagedCodexHome()
-
-    const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
-    expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
-    expect(runtimeConfig).toContain('trust_level = "untrusted"')
-    expect(runtimeConfig).not.toContain('trust_level = "trusted"')
-  })
+  it.each(['"trust_level"', "'trust_level'"])(
+    'applies a CRLF system revocation with key %s to an LF runtime project',
+    (trustKey) => {
+      mkdirSync(join(userDataDir, 'codex-runtime-home', 'home'), { recursive: true })
+      writeFileSync(
+        getRuntimeConfigPath(),
+        ["[projects.'c:\\repo']", 'trust_level = "trusted"', ''].join('\n'),
+        'utf-8'
+      )
+      writeFileSync(
+        getSystemConfigPath(),
+        ['[projects."C:/repo"]', `${trustKey} = "untrusted"`, ''].join('\r\n'),
+        'utf-8'
+      )
+
+      syncSystemConfigIntoManagedCodexHome()
+
+      const runtimeConfig = readFileSync(getRuntimeConfigPath(), 'utf-8')
+      expect(runtimeConfig.match(/\[projects\./g)).toHaveLength(1)
+      expect(runtimeConfig).toContain('trust_level = "untrusted"')
+      expect(runtimeConfig).not.toContain('trust_level = "trusted"')
+    }
+  )
🧰 Tools
🪛 OpenGrep (1.30.0)

[ERROR] 142-144: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

@AmoabaKelvin

Copy link
Copy Markdown
Collaborator

Thanks for looking into this @leilei3167

Could you add regression coverage in codex-config-mirror.test.ts too? The mirror uses the same parser, so this fix covers that path, but the new tests only exercise the trust updater. A system config with ["projects"."/ p"] and a runtime config with [projects."/p"] should produce exactly one table for that path after mirroring.

Looping in @nwparker on recovery for users who already hit this. The updated mirror can deduplicate Orca’s managed config, but existing duplicates in ~/.codex/config.toml remain, so running Codex directly can still fail after upgrading. Should we track a targeted repair separately or document the manual cleanup? Any repair would need to preserve unrelated settings in the user’s config, which we normally try to avoid.

…2592)

Add coverage in codex-config-mirror.test.ts for a system config with
["projects"."…"] and a runtime config with [projects."…"] for the
same path, asserting mirror leaves exactly one project table.
@leilei3167

Copy link
Copy Markdown
Contributor Author

Added deduplicates a Codex quoted-key system project header against a bare runtime header in codex-config-mirror.test.ts: system config uses ["projects"."…"], runtime has [projects."…"] for the same path, and after syncSystemConfigIntoManagedCodexHome the runtime config has exactly one projects table. Pushed in 2a05bf7.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Re-reviewed the delta since the prior pullfrog review (be61fa8): one regression test added to the Codex config mirror suite. No production code changed in this commit.

  • Quoted-key mirror dedup test — codex-config-mirror.test.ts now mirrors a basic-quoted system project header (["projects"."…"]) against a bare runtime header and asserts exactly one project table survives. Verified it fails against the pre-fix header matcher (expected 1, received 2), so it exercises the bug rather than passing vacuously.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@nwparker

nwparker commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Closing as superseded by #23958, which is merged and present on main.

I checked this PR's scope and discussion against the merged implementation. The replacement explicitly supersedes this PR and covers its requested behavior. Thanks for the contribution; the branch is preserved.

@nwparker nwparker closed this Oct 6, 2026
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.

[Bug]: Codex config.toml gets a duplicate [projects."…"] table (Orca does not recognize Codex's ["projects"."…"] headers), breaking codex startup

3 participants