Skip to content

Commit 4ba390d

Browse files
cm-dyoshikawaclaude
andcommitted
docs: fix the round-trip claim for canonical .git rules while opted out
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>
1 parent 56f79d5 commit 4ba390d

2 files changed

Lines changed: 2 additions & 2 deletions

File tree

docs/reference/file-formats.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ Relative filesystem globs such as `src/**` or `**/*.tf` are emitted under `permi
977977
978978
The `:workspace_roots` table also receives a default `.git` carve-out: `".git/**" = "write"`. Codex's `:workspace` baseline keeps `.git` read-only inside workspace roots, which denies basic git workflows (commit/stage operations write to `.git/index`, `.git/objects`, refs, and logs; everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to `.git/config`). The write rule reopens the whole subtree, including `.git/config` — an earlier `".git/config" = "read"` security guard (a writable `.git/config` lets a sandboxed process set keys like `core.fsmonitor` or `core.hooksPath` that execute code outside the sandbox) was dropped because it blocked those everyday commands while the protection it added was already partial (`.git/hooks/`, and `.git/modules/**` for submodules, remains writable so hook managers such as lefthook and simple-git-hooks keep working; a sandboxed process could still install a hook directly). Users who want stricter isolation can author a more specific rule (e.g. `read: { ".git/config": "allow" }` or `read: { ".git/hooks/**": "allow" }`) in the canonical permissions, which wins over the default (Codex resolves the more specific path with priority). Because `.git/**` is an unbounded `**` pattern, the carve-out also means `glob_scan_max_depth = 8` is effectively always emitted unless it is suppressed.
979979
980-
The carve-out is skipped in three cases: a user rule for the same pattern always wins per key; the `codexcli.git_write_rules` override set to `false` suppresses it entirely (only an explicit `false` does; the default is `true`); and it is not injected when `codexcli.base_permission_profile` is `":read-only"` (it would grant `.git` write access inside a sandbox the user explicitly chose to keep read-only) or when the canonical rules contain a direct `":workspace_roots"` pattern (a whole-tree access decision that the defaults must not override). Like `:minimal`, the default-valued carve-out is not imported into the Rulesync model on `rulesync import` — it is re-added on every generate — while customized `.git` values import normally. One limitation: the `git_write_rules` flag itself cannot be recovered from `config.toml`, so it does not round-trip through `rulesync import`; if you opted out with `false`, re-add the flag to the canonical permissions config after importing (and if you want the same `.git` rules while opted out, author them as canonical `read`/`write` rules, which round-trip normally, rather than hand-writing the exact default pair in `config.toml`, which import skips). Migration note: configs generated before the `".git/config" = "read"` default was removed still carry that entry, and `rulesync import` now treats it as a user-authored rule — it lands in the canonical config as `read: { ".git/config": "allow" }` and, because Codex gives the more specific path priority, keeps `.git/config` read-only on every regenerate. If you want the current writable default instead, delete that rule from the canonical permissions after importing.
980+
The carve-out is skipped in three cases: a user rule for the same pattern always wins per key; the `codexcli.git_write_rules` override set to `false` suppresses it entirely (only an explicit `false` does; the default is `true`); and it is not injected when `codexcli.base_permission_profile` is `":read-only"` (it would grant `.git` write access inside a sandbox the user explicitly chose to keep read-only) or when the canonical rules contain a direct `":workspace_roots"` pattern (a whole-tree access decision that the defaults must not override). Like `:minimal`, the default-valued carve-out is not imported into the Rulesync model on `rulesync import` — it is re-added on every generate — while customized `.git` values import normally. One limitation: the `git_write_rules` flag itself cannot be recovered from `config.toml`, so it does not round-trip through `rulesync import`; if you opted out with `false`, re-add the flag to the canonical permissions config after importing (and if you want the same `.git` rules while opted out, author them as canonical `read`/`write` rules rather than hand-writing them in `config.toml` — though note that import cannot tell a user-authored `".git/**" = "write"` from the default carve-out, so that exact pattern/value pair is still skipped on import and must be re-authored in the canonical config afterwards). Migration note: configs generated before the `".git/config" = "read"` default was removed still carry that entry, and `rulesync import` now treats it as a user-authored rule — it lands in the canonical config as `read: { ".git/config": "allow" }` and, because Codex gives the more specific path priority, keeps `.git/config` read-only on every regenerate. If you want the current writable default instead, delete that rule from the canonical permissions after importing.
981981
982982
The generated `[permissions.rulesync]` profile always extends one of Codex's built-in permission profiles via `extends`. The baseline is chosen with the `codexcli.base_permission_profile` override key (`":read-only"` | `":workspace"` | `":danger-full-access"`) and defaults to `":workspace"` when unspecified. Codex's built-in `:workspace` baseline grants read access to the whole filesystem and write access to the entire workspace root plus `/tmp` and `$TMPDIR` (with carve-outs protecting `.git`, `.codex`, and `.agents`), while `:read-only` keeps command execution read-only; the generated `filesystem` entries then grant or deny access on top of the chosen baseline. Codex's third built-in profile, `:danger-full-access`, is rejected by `extends` at Codex config load time — so selecting it works differently: Rulesync emits `default_permissions = ":danger-full-access"` directly and skips the managed `[permissions.rulesync]` profile entirely (with the sandbox removed there is nothing for filesystem/network rules to refine; canonical `read`/`edit`/`write`/`webfetch` rules are ignored for Codex CLI with a warning, and any stale managed profile from a previous generate is pruned while sibling hand-written profiles are preserved). On import, a profile's `extends` value round-trips back into `codexcli.base_permission_profile` when it names one of the two extendable built-ins, and a top-level `default_permissions = ":danger-full-access"` round-trips the same way; a custom parent profile is skipped and replaced by the managed baseline on regeneration (with a warning).
983983

skills/rulesync/file-formats.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -977,7 +977,7 @@ Relative filesystem globs such as `src/**` or `**/*.tf` are emitted under `permi
977977
978978
The `:workspace_roots` table also receives a default `.git` carve-out: `".git/**" = "write"`. Codex's `:workspace` baseline keeps `.git` read-only inside workspace roots, which denies basic git workflows (commit/stage operations write to `.git/index`, `.git/objects`, refs, and logs; everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to `.git/config`). The write rule reopens the whole subtree, including `.git/config` — an earlier `".git/config" = "read"` security guard (a writable `.git/config` lets a sandboxed process set keys like `core.fsmonitor` or `core.hooksPath` that execute code outside the sandbox) was dropped because it blocked those everyday commands while the protection it added was already partial (`.git/hooks/`, and `.git/modules/**` for submodules, remains writable so hook managers such as lefthook and simple-git-hooks keep working; a sandboxed process could still install a hook directly). Users who want stricter isolation can author a more specific rule (e.g. `read: { ".git/config": "allow" }` or `read: { ".git/hooks/**": "allow" }`) in the canonical permissions, which wins over the default (Codex resolves the more specific path with priority). Because `.git/**` is an unbounded `**` pattern, the carve-out also means `glob_scan_max_depth = 8` is effectively always emitted unless it is suppressed.
979979
980-
The carve-out is skipped in three cases: a user rule for the same pattern always wins per key; the `codexcli.git_write_rules` override set to `false` suppresses it entirely (only an explicit `false` does; the default is `true`); and it is not injected when `codexcli.base_permission_profile` is `":read-only"` (it would grant `.git` write access inside a sandbox the user explicitly chose to keep read-only) or when the canonical rules contain a direct `":workspace_roots"` pattern (a whole-tree access decision that the defaults must not override). Like `:minimal`, the default-valued carve-out is not imported into the Rulesync model on `rulesync import` — it is re-added on every generate — while customized `.git` values import normally. One limitation: the `git_write_rules` flag itself cannot be recovered from `config.toml`, so it does not round-trip through `rulesync import`; if you opted out with `false`, re-add the flag to the canonical permissions config after importing (and if you want the same `.git` rules while opted out, author them as canonical `read`/`write` rules, which round-trip normally, rather than hand-writing the exact default pair in `config.toml`, which import skips). Migration note: configs generated before the `".git/config" = "read"` default was removed still carry that entry, and `rulesync import` now treats it as a user-authored rule — it lands in the canonical config as `read: { ".git/config": "allow" }` and, because Codex gives the more specific path priority, keeps `.git/config` read-only on every regenerate. If you want the current writable default instead, delete that rule from the canonical permissions after importing.
980+
The carve-out is skipped in three cases: a user rule for the same pattern always wins per key; the `codexcli.git_write_rules` override set to `false` suppresses it entirely (only an explicit `false` does; the default is `true`); and it is not injected when `codexcli.base_permission_profile` is `":read-only"` (it would grant `.git` write access inside a sandbox the user explicitly chose to keep read-only) or when the canonical rules contain a direct `":workspace_roots"` pattern (a whole-tree access decision that the defaults must not override). Like `:minimal`, the default-valued carve-out is not imported into the Rulesync model on `rulesync import` — it is re-added on every generate — while customized `.git` values import normally. One limitation: the `git_write_rules` flag itself cannot be recovered from `config.toml`, so it does not round-trip through `rulesync import`; if you opted out with `false`, re-add the flag to the canonical permissions config after importing (and if you want the same `.git` rules while opted out, author them as canonical `read`/`write` rules rather than hand-writing them in `config.toml` — though note that import cannot tell a user-authored `".git/**" = "write"` from the default carve-out, so that exact pattern/value pair is still skipped on import and must be re-authored in the canonical config afterwards). Migration note: configs generated before the `".git/config" = "read"` default was removed still carry that entry, and `rulesync import` now treats it as a user-authored rule — it lands in the canonical config as `read: { ".git/config": "allow" }` and, because Codex gives the more specific path priority, keeps `.git/config` read-only on every regenerate. If you want the current writable default instead, delete that rule from the canonical permissions after importing.
981981
982982
The generated `[permissions.rulesync]` profile always extends one of Codex's built-in permission profiles via `extends`. The baseline is chosen with the `codexcli.base_permission_profile` override key (`":read-only"` | `":workspace"` | `":danger-full-access"`) and defaults to `":workspace"` when unspecified. Codex's built-in `:workspace` baseline grants read access to the whole filesystem and write access to the entire workspace root plus `/tmp` and `$TMPDIR` (with carve-outs protecting `.git`, `.codex`, and `.agents`), while `:read-only` keeps command execution read-only; the generated `filesystem` entries then grant or deny access on top of the chosen baseline. Codex's third built-in profile, `:danger-full-access`, is rejected by `extends` at Codex config load time — so selecting it works differently: Rulesync emits `default_permissions = ":danger-full-access"` directly and skips the managed `[permissions.rulesync]` profile entirely (with the sandbox removed there is nothing for filesystem/network rules to refine; canonical `read`/`edit`/`write`/`webfetch` rules are ignored for Codex CLI with a warning, and any stale managed profile from a previous generate is pruned while sibling hand-written profiles are preserved). On import, a profile's `extends` value round-trips back into `codexcli.base_permission_profile` when it names one of the two extendable built-ins, and a top-level `default_permissions = ":danger-full-access"` round-trips the same way; a custom parent profile is skipped and replaced by the managed baseline on regeneration (with a warning).
983983

0 commit comments

Comments
 (0)