Skip to content

fix: drop the Codex CLI .git/config read carve-out and keep only .git/** write#2294

Merged
dyoshikawa merged 3 commits into
mainfrom
resolve-scrap-issue-2279-codex-git-config-writable
Jul 17, 2026
Merged

fix: drop the Codex CLI .git/config read carve-out and keep only .git/** write#2294
dyoshikawa merged 3 commits into
mainfrom
resolve-scrap-issue-2279-codex-git-config-writable

Conversation

@dyoshikawa

Copy link
Copy Markdown
Owner

Summary

Drops the default ".git/config" = "read" carve-out that rulesync emitted into the Codex CLI managed profile's :workspace_roots table, keeping only ".git/**" = "write".

In practice git is not usable without write access to .git/config: everyday commands such as git remote add/set-url, git push -u (records branch.<name>.remote/merge), local-scope git config, and clone/submodule flows all write to it. Meanwhile the protection the read guard added was already partial by design (.git/hooks/ stays writable, a maintainer decision on #2272), so the cost/benefit did not hold. Making it worse, the default was injected per exact key (??=), so even a user-authored ".git/**": "allow" write rule could not suppress it — Codex resolves the more specific path with priority, keeping .git/config read-only against the user's explicit intent.

Users who want stricter isolation can still author their own override (e.g. read: { ".git/config": "allow" }) in the canonical permissions; the codexcli.git_write_rules: false opt-out stays as is.

Changes

  • src/features/permissions/codexcli-permissions.ts: remove ".git/config": "read" from CODEX_GIT_WRITE_RULES and update the rationale comments (generate and import both key off the same constant, so the import-skip logic needed no code change).
  • src/types/permissions.ts: update the git_write_rules doc comment.
  • Tests: codexcli-permissions.test.ts (default emission, import of legacy ".git/config" = "read" as a user rule, round-trip) and e2e-permissions.spec.ts.
  • docs/faq.md: rewrite the carve-out paragraph, and extend the codexcli.permission example with the practical workspace write set (".", ".git/**", ".agents/**", ".codex/**") plus an explanatory bullet — a tool-scoped category replaces the shared one wholesale, and Codex's :workspace baseline keeps .git, .agents, and .codex read-only inside workspace roots (codex-rs FileSystemSandboxPolicy::workspace_write).
  • docs/reference/file-formats.md: rewrite the carve-out paragraphs (no longer claims .git/config stays read-only).
  • skills/rulesync/*: regenerated via scripts/sync-skill-docs.ts.

Note: the issue proposed "./**" = "write" for the workspace-subtree entry, but Codex rejects :workspace_roots subpath keys containing . or .. components (Codex permissions reference — "Other subpaths must be relative descendants and cannot contain . or .. components"; the documented base form is "."), so the example uses ".": "allow" instead.

Closes #2279

🤖 Generated with Claude Code

cm-dyoshikawa and others added 3 commits July 16, 2026 18:52
…/** write

Everyday git commands (git remote add/set-url, git push -u, local-scope
git config, clone/submodule flows) write to .git/config, so the default
'".git/config" = "read"' rule broke basic workflows while the
protection it added was already partial by design (.git/hooks/ stays
writable). The default carve-out is now '".git/**" = "write"' only;
stricter users can author their own read override in the canonical
permissions.

Also extend the FAQ's codexcli.permission example with the practical
workspace write set ('.', '.git/**', '.agents/**', '.codex/**') since a
tool-scoped category replaces the shared one wholesale and Codex's
:workspace baseline keeps .git, .agents, and .codex read-only. The
workspace-root entry uses '.' rather than './**' because Codex rejects
subpath keys containing '.' components.

Closes #2279

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Warn about the security trade-off of granting .codex/** and .agents/**
  write access in the FAQ example (sandbox self-reconfiguration and
  persistent instruction injection).
- Note the round-trip exception for the ".git/**" write rule matching
  the default carve-out, and how to restore it after opting out.
- Restore the description of what the dropped .git/config guard
  protected against (core.fsmonitor / core.hooksPath).
- Add a migration note: legacy configs carrying '".git/config" = "read"'
  now import as a user-authored rule that must be deleted manually to get
  the writable default.
- Singularize remaining 'carve-outs' test names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The advice to author .git rules canonically when git_write_rules is
false claimed they round-trip normally, but import cannot distinguish a
user-authored '".git/**" = "write"' from the default carve-out and
skips that exact pair, contradicting the FAQ's exception note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dyoshikawa
dyoshikawa merged commit 3ff68f6 into main Jul 17, 2026
9 checks passed
@dyoshikawa

Copy link
Copy Markdown
Owner Author

@dyoshikawa Thank you!

@dyoshikawa
dyoshikawa deleted the resolve-scrap-issue-2279-codex-git-config-writable branch July 17, 2026 02:20
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.

Codex CLI: drop the ".git/config" = "read" carve-out and keep only ".git/**" = "write"

2 participants