| name | sync-claude-permissions |
| description | Translates current-project Codex permission rules (.codex/rules/default.rules prefix_rule blocks only, not personal ~/.codex rules) into Claude Code permission entries (.claude/settings.local.json), computes the gap against already-present rules, and presents portable rules for per-rule approval grouped by category before writing. Global and project-agnostic. Trigger when the user says "sync claude permissions", "sync-claude-permissions", "port project codex rules to claude", "translate project codex permissions", "replicate project codex allow rules", or "bring project codex permissions into claude". |
Sync Project Codex Permissions To Claude Code
Global, project-agnostic skill. Reads only the current project's
.codex/rules/default.rules, translates prefix_rule(...) entries to Claude Code
Bash(...) permission syntax, diffs against .claude/settings.local.json, and writes
only the user-approved new entries. Every proposed rule requires explicit per-rule
approval before any write.
This skill intentionally syncs project-local rules only. Do not read or fall back to
${CODEX_HOME:-~/.codex}/rules/default.rules: personal/global Codex rules may affect the
active Codex session, but they are outside this project-local sync workflow.
Complements setup-permissions (which predicts rules from the stack) by operating on
already-written project-local Codex rules rather than generating from scratch. Treat
.codex/rules/default.rules as the source for this operation even when its rules were
originally adapted from Claude Code settings.
Translation reference
Pattern → Claude rule
Reconstruct the longest unambiguous prefix from the pattern array:
Codex pattern | Claude rule(s) |
|---|
["git", "status"] | Bash(git status:*) |
["git", ["status", "diff", "log"]] | Bash(git status:*), Bash(git diff:*), Bash(git log:*) |
["git", "-C", "/repo", ["status", "log"]] | Bash(git -C /repo status:*), Bash(git -C /repo log:*) |
["npm", "run", "test"] | Bash(npm run test:*) |
["./install.sh"] | Bash(./install.sh:*) |
When a pattern contains nested lists (one-of alternatives), expand each alternative into
one Claude rule. If expansion would produce more than 8 rules from a single source entry,
collapse to the common prefix and warn the user that the translation is broader.
Decision mapping
Codex decision | Claude target |
|---|
"allow" | permissions.allow |
"deny" | permissions.deny |
"prompt" | NOT ported — Claude Code's default for uncovered commands is already to prompt; no rule needed |
Not portable
decision = "prompt" → list in "Deliberately NOT ported"; never add to allow or deny.
- Absolute-path patterns (e.g.
["cp", "/home/user/...", "..."]) → flag as
"manually review — contains absolute paths"; never auto-port.
Output notation
Always use Bash(prefix:*) colon notation (e.g. Bash(git status:*), never
Bash(git status *)).
Procedure
1. Read source
Read .codex/rules/default.rules relative to the current working directory. If the file
does not exist, report it and stop - do not create it and do not fall back to
${CODEX_HOME:-~/.codex}/rules/default.rules.
Parse all prefix_rule(...) blocks. A block spans from prefix_rule( to its matching
). Within each block extract:
pattern — the inline TOML array (may span multiple lines); parse it as a whole unit.
decision — "allow", "deny", or "prompt".
justification — informational string; surface it in the approval prompt.
2. Read target
Read .claude/settings.local.json if it exists; if not, treat permissions.allow and
permissions.deny as empty (the file will be created in step 5). Also peek at
.claude/settings.json for already-present entries to avoid duplicates across both files,
but write exclusively to .claude/settings.local.json.
Parse permissions.allow and permissions.deny arrays.
3. Compute gap
For each prefix_rule block:
- Apply the translation rules above to get one or more candidate Claude rule strings.
- Check if each candidate is already present in the target's allow/deny list.
- Classify it as one of:
- New — candidate not in target; will be proposed.
- Already synced — candidate already present; skip silently.
- Not portable —
decision = "prompt", absolute-path pattern, or expansion > 8
entries (collapse case).
If every rule is either already synced or not portable, report "Already in sync — nothing
to add." and stop.
4. Show the gap summary
Before asking for approval, display:
Rules to add (N new)
allow:
- Bash(git status:*) ← allows git status; does NOT allow git push or git reset
...
deny:
- Bash(rm:*)
Already synced (M rules, skipped)
- Bash(./install.sh:*)
...
Deliberately NOT ported
- [git reset / checkout / push] decision = "prompt" — already Claude Code default behavior
- [cp /home/user/...] absolute paths — flag for manual review
5. Propose per-rule approval
Use AskUserQuestion with multiSelect: true, grouping proposed rules by category
(git / build-test / package-manager / project-scripts / other). For each rule show:
- The proposed rule string (e.g.
Bash(git status:*))
- A one-line note from
justification: "allows X; does NOT allow Y"
Also display the "Deliberately NOT ported" section as informational (not a question),
so the user sees what was withheld and why.
If there is only one category, a single multiSelect question is fine.
6. Write approved rules
For each approved rule:
Echo back exactly what was written and to which file.
Guardrails
- Source is project-local and read-only. Read only the current project's
.codex/rules/default.rules; never read personal ${CODEX_HOME:-~/.codex}/rules/default.rules
as a fallback, and never modify the project-local source file.
- Propose first, write after approval. Never auto-write any rule.
decision = "prompt" is always NOT ported. Surface it in "Deliberately NOT ported"
with the note "already Claude Code default behavior (prompt); no rule needed." Never add
it to allow or deny.
- Absolute-path patterns are never auto-ported. Flag them for manual review; let the
user decide whether a portable equivalent makes sense.
- Write only to
.claude/settings.local.json. Never touch .claude/settings.json,
global ~/.claude/settings.json, or any Codex file.
- Narrow notation only. Every
Bash(...) rule must use prefix:* colon notation.
- Merge, never clobber. Preserve existing entries; deduplicate; never overwrite the
file from scratch.
- Stop gracefully if the source file is missing or the gap is empty.