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 proje...
Scanned 6/9/2026
Install via CLI
openskills install ada-ggf25/AI-Tools---
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:
```text
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:
- Append to `permissions.allow` or `permissions.deny` in `.claude/settings.local.json`.
- If the file does not exist, create it with the structure:
```json
{
"permissions": {
"allow": [],
"deny": []
}
}
```
- If the file exists but lacks the `permissions` key, add it.
- Deduplicate: never write a rule already present.
- Preserve all existing entries and formatting as closely as possible.
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.
No comments yet. Be the first to comment!