Applying a permission policy
Translation is best-effort — read this first
This skill provides the policy schema and OpenRig's best current understanding of how to translate it to each harness. That guidance is best-effort and version-stamped: harness permission surfaces change frequently across versions, so a mapping that is correct today can drift on a later release. When you apply a config-surface policy:
- Verify against the current docs for your installed harness version. Treat this skill's tables as version-stamped starting points (see
tested_against), not settled truth.
- Prefer testing in a protected or disposable environment first. An incorrect write can lock a seat out or over-permit it, so confirm the format before applying it to a live seat.
- Hand-editing via the harness's own settings tools is always a valid path. If a translation is uncertain, setting the policy directly — or asking the user to — is a legitimate outcome, not a failure.
The reliable, deterministic parts are the blunt instruments: the YOLO full-bypass flag and the floor (Claude acceptEdits / Codex workspace-write), which ride the stable launch-flag surface. Fine-grained config-rule translation — the allow/ask/deny prefix rules and the Codex posture — is the best-effort part and carries no guarantee of exact fidelity. If a translation looks wrong, fall back to a blunt instrument or a direct hand-edit.
⚠ Read this preamble first — why this is a SKILL, not a script (do not "improve" it into a deterministic updater)
Harness permission formats are a moving target. The exact fact this skill is stamped against — Claude Code 2.1.220 permission rules are prefix-only (no flag-precise Bash match) — is version-specific; the rule grammar can change on the next point release. A hardcoded/deterministic updater would foot-gun the instant it does, and a wrong permission write can lock a seat out or silently over-permit it. So the maintained artifact is this caveated, version-stamped skill that an agent reads and applies interactively, grounding in the live format — never a code path that writes blindly. If you are tempted to replace this with a deterministic projector: that temptation is the bug this preamble exists to stop.
When to reach for this
- A rig/seat has a policy attached (
rig.yaml … permission_policy: <file>), and you're at setup/preflight — apply it (mandatory for autonomous seats, see the freeze warning).
- A user asks "apply the Standard/Open/Locked/Operator policy here" or "set up permissions like ".
- NOT for authoring policies (that's the policy spec) and NOT for daemon-level enforcement (OpenRig is agnostic — it injects nothing).
What you're translating (the boundary)
INPUT = a harness-neutral policy spec (semantic action schema): default_posture, floor, allow/ask/deny as semantic actions (push_to_remote, delete_files, …), destructive_class, source: builtin|custom. You TRANSLATE that intent → the harness's live config. You never invent policy; you never pre-bake harness config into the spec.
The apply flow (interactive — the interactive-ask IS the product)
- Read the policy spec + its
source: marker (built-in read-only from the package = copy-to-customize before editing; custom = user space).
- PREFLIGHT (mandatory) — ground yourself in THIS environment:
- daemon-HOME must equal seat-HOME, or a user-level Claude write never reaches the seat. Check; if they differ, stop and ask — do not write into the wrong HOME.
- Read the harness's actual current config file + confirm its schema against your installed version (
claude --version, the live settings.json shape, codex/pi versions). If the format differs from this skill's tested_against, trust the LIVE format and flag the drift.
- Derive the per-harness config from the semantic actions (see the three legs below). Compute the Codex posture-collapse + Pi bit.
- Show the concrete DIFF before writing (old → new, per file). Never a blind write.
- Resolve best-effort actions by the built-in's rule FIRST, then ask only on genuine ambiguity. Best-effort destructive actions (
force_push, delete_files, delete_everything, read_secrets) default to ask (never a deny that can't be enforced) — apply that default rather than stopping to question-back. Surface a question only when the policy is genuinely ambiguous or the seat is interactive. For a same-prefix collision (push_to_remote vs force_push, both Bash(git push:*)), NEVER ask — apply the deterministic PREFIX-COLLISION rule in the Claude leg. On a Claude-only fleet, if the policy denies network_egress, say so explicitly: it is NOT enforceable — Claude has no network gate, a Bash(curl:*)/Bash(wget:*) deny is best-effort, and python/node fetch bypasses it (live-verified: urllib reached HTTPS 200 under a curl/wget deny). Route real egress-gating to the Codex sandbox, or record it as advisory on Claude.
- Back up the current config, then write. Prefer the user-level file (
~/.claude/settings.json) for durable policy — it's the clobber-resistant tier (deny wins across the union). ALWAYS include defaultMode: "acceptEdits" in the Claude settings.json you write — the floor key must be PRESENT in the file, or the config policy strips the floor.
- Verify the write landed and the seat can see it (re-read; confirm
defaultMode: "acceptEdits" is present; for an autonomous seat, confirm no ask will freeze it).
Two surfaces — and this skill writes only ONE of them
Permissions live on two surfaces with opposite stability — which is the whole reason for the agent-driven split:
- LAUNCH-FLAG surface — STABLE (>1yr) → OpenRig sets it DETERMINISTICALLY, NOT this skill. The flags OpenRig passes at boot: Claude
--permission-mode / --dangerously-skip-permissions; Codex sandbox/bypass flags; Pi --approve/--no-approve. Two things live here, both OpenRig-owned: the FLOOR (minimum, by default) and YOLO (full bypass, opt-in).
- CONFIG-FILE surface — CHAOTIC → NEVER deterministic → THIS SKILL'S DOMAIN. Claude
settings.json allow/ask/deny + defaultMode; Codex config.toml approval rules. This is where the Locked / Standard / Open policies get applied — agent-driven, version-stamped, caveated. (Pi has no permission surface at all — its --approve/--no-approve flags govern project-resource trust, not tool permissions (VM-confirmed Pi 0.83.0); a permission policy does not translate to Pi. See the Pi leg.)
THE FLOOR (launch-flag, OpenRig-set — this skill KNOWS it, never WRITES it): Claude --permission-mode acceptEdits (keep it); Codex workspace-only (stop forcing danger-full-access — ≈ Codex's own default ≈ setting-nothing = agnostic); Pi --no-approve (a project-resource-trust floor — Pi has no permission gate; see the Pi leg). One consistent unconditional minimum. A chosen config-file policy layers ON TOP of the floor.
YOLO MODE (launch-flag, OpenRig-set, opt-in — for people done with permissions): OpenRig boots every seat with the full-bypass flag (Claude --dangerously-skip-permissions; Codex full-bypass). When YOLO is on, CONFIG-FILE POLICY IS MOOT — this skill does NOT apply a config policy (or explicitly notes it's overridden by the flag); don't fight the bypass. YOLO is OpenRig's deterministic job, not this skill's. (Operator = YOLO mode — subsumed.)
The three harness legs (skill-owned mapping knowledge, version-stamped + caveated)
Claude — ~/.claude/settings.json (user-level, clobber-resistant)
Action → prefix map (VM-CONFIRMED @ Claude 2.1.220, 2026-08-03 — every row exercised through real Claude tool calls). fidelity: clean = the prefix honors intent; best_effort = prefix-only can't fully enforce → prefer ask, never a deny that lies.
| Semantic action |
permissions.* prefix rule(s) |
fidelity |
| push_to_remote |
Bash(git push:*) |
clean |
| force_push |
ask the flag-first forms Bash(git push --force:*) Bash(git push -f:*) Bash(git push --force-with-lease:*); base Bash(git push:*) follows push_to_remote (see the collision rule) |
best_effort — catches flag-FIRST force; flag-LAST (git push origin main --force) leaks |
| create_pr |
Bash(gh pr create:*) |
clean |
| publish_package |
Bash(npm publish:*) Bash(pnpm publish:*) Bash(yarn publish:*) Bash(cargo publish:*) |
clean (multi-pattern) |
| merge_or_release |
Bash(git merge:*) Bash(git tag:*) Bash(gh release:*) |
best_effort — protected-branch semantics not expressible |
| delete_files |
Bash(rm:*) |
best_effort — target-first leak (rm <t> -rf slips); scope not boundable → default ask |
| delete_everything |
Bash(rm -rf:*) Bash(rm -fr:*) |
best_effort — same leak → default to ask, never a silent deny |
| reset_or_discard_vcs |
Bash(git reset:*) Bash(git clean:*) Bash(git checkout:*) Bash(git branch -D:*) |
best_effort |
| drop_persistent_store |
Bash(dropdb:*) Bash(docker volume rm:*) (+ project-specific) |
best_effort (open-ended) → ask |
| rig_up / rig_down |
Bash(rig up:*) / Bash(rig down:*) |
clean — ⚠ an ask here FREEZES an autonomous seat |
| mutate_topology |
Bash(rig add:*) Bash(rig remove:*) Bash(rig rename:*) |
clean |
| run_toolchain |
Bash(npm:*) Bash(npx:*) Bash(pnpm:*) Bash(yarn:*) Bash(node:*) Bash(tsc:*) Bash(vitest:*) Bash(jest:*) |
clean (the known set) |
| run_arbitrary_shell |
Bash(*) — this IS the default_posture knob |
clean |
| network_egress |
Bash(curl:*) Bash(wget:*) (partial) |
best_effort — Claude has NO native network gate; real floor = Codex sandbox (route it there) |
| read_secrets |
Read(./.env) Read(~/.ssh/**) Read(**/*secret*) (path-scoped) |
best_effort — path-based, not secret-aware |
| install_dependencies |
Bash(npm install:*) Bash(pip install:*) Bash(brew install:*) |
clean |
PREFIX-ONLY caveat — cannot flag-precisely gate (force_push vs push_to_remote both match Bash(git push:*)); target-first leak (rm <target> -rf slips a Bash(rm -rf*) deny). Actions marked best_effort in the taxonomy (force_push, delete_files, read_secrets) → surface the caveat and prefer ask over a deny you can't enforce (a deny that leaks lies).
PREFIX-COLLISION resolution (deterministic — resolve it, NEVER ask). When two actions collapse to the SAME base prefix with different dispositions — the canonical case is push_to_remote=allow + force_push=ask, both basing to Bash(git push:*) — you cannot put that base prefix in both allow and ask. Resolve without a question-back:
- Push allowed + force asked (Standard / Open): put the base
Bash(git push:*) per push's disposition (allow), AND add the flag-first force forms to ask (Bash(git push --force:*), Bash(git push -f:*), Bash(git push --force-with-lease:*)). RECORD in your diff that this is best-effort — flag-LAST force (git push origin main --force) leaks past the flag-first ask rules and runs under the allow. Don't block on it.
- Push denied (Locked):
Bash(git push:*) in deny already covers force — no conflict.
This is a FIXED rule, not a user decision — apply it and note the leak.
defaultMode: "acceptEdits" is the FLOOR and MUST be written into every Claude settings.json you produce. It is not a knob you tune, but it IS a key you must always include — a config policy that omits it strips the floor. Live-confirmed schema: {"permissions":{"allow":[],"ask":[],"deny":[],"defaultMode":"acceptEdits"}}.
Codex — ~/.codex/config.toml (via the existing codex_config_fragment splice path — reuse, don't invent)
Codex is per-posture, not per-action: the whole action set collapses to the nearest {sandbox_mode, approval_policy} pair. Built-in → posture (VM-CONFIRMED @ Codex 0.120.0, 2026-08-03 — each posture applied to a real seat: read-only blocked writes+egress, workspace-write allowed writes/blocked egress, danger-full-access allowed both):
| Built-in |
sandbox_mode |
approval_policy |
Collapse note (surface this to the user) |
| Locked |
read-only |
on-request |
read-only blocks all writes + egress, prompts on escalation — OVER-restricts edit_files vs the Claude floor |
| Standard ⭐ |
workspace-write |
on-request |
workspace writes allowed, egress blocked by sandbox, prompt on escalation — UNDER-gates create_pr (no per-action ask; folds into the prompt) |
| Open |
danger-full-access |
on-failure |
everything allowed, prompt only on failure — OVER-permits the destructive_class vs the Claude ask-gates |
| YOLO |
(flag-surface — full-bypass launch flag, NOT config.toml) |
— |
not a config write; the rip-out adapter owns it |
Valid values (VM-confirmed @ Codex 0.120.0): sandbox_mode ∈ {read-only, workspace-write, danger-full-access}; approval_policy ∈ {on-request, on-failure, never, untrusted, granular} (granular observed live at 0.120.0). ⚠ on-failure (used by Open) is marked deprecated in 0.120.0 help but still accepted — revisit if a future Codex drops it.
Surface the collapse: "Codex can't gate per-action; this policy becomes sandbox=X approval=Y, which over-permits Z / under-permits W."
Pi — NO permission surface at all (VM-confirmed Pi 0.83.0 — the earlier "approval bit" reading was WRONG)
- Pi has no tool-permission gate — neither a config store NOR a permission-posture flag. Live verification confirmed: Pi 0.83.0's
--approve/-a and --no-approve/-na govern project-resource trust (whether project extensions/resources appear in RPC get_commands), NOT tool-call approval. There is no sandbox or permission mechanism to map a policy onto.
- So a permission policy does not translate to Pi. Do NOT say
--approve = "auto-approve all", or that a posture "collapses to a Pi approval bit" — that was an earlier misread, corrected here. This skill computes nothing for Pi.
- Whatever the OpenRig adapter sets (
--no-approve by default) is a project-resource-trust floor, not a permission posture — not this skill's to derive.
- Behavior to know (resource-trust, NOT permission-gating): under default ask /
--no-approve, a project resource is silently absent from RPC get_commands; it appears under --approve. Never mistake that for permission enforcement.
- Say this to the user for a Pi seat: "Pi has no permission-policy surface today — the policy applies to Claude/Codex; Pi runs under its project-resource-trust flag unchanged."
⚠ The two warnings that save real fleets
ask FREEZES an autonomous/headless Claude seat — it hangs on the first non-edit op. So for autonomous fleets apply Open/YOLO (or a policy with no asks) at setup — the floor only covers edits; bash still asks without an applied policy. Never leave an autonomous seat on a policy with live ask gates. (Pi has no permission ask at all — see the Pi leg; Codex prompts per its posture, not per-action.)
- daemon-HOME ≠ seat-HOME silently voids user-level writes — always the preflight check above.
The built-ins — DEFINED by the shipped policy spec files (translate FROM the spec; POINT, don't COPY)
The four built-in policies ship as read-only spec files (source: builtin, policy_schema_version: 1) — they are the AUTHORITATIVE definitions. Do NOT restate their action-sets here. The selected spec supplies the action sets; a second copy in this skill can diverge. Read the attached/selected spec and translate its semantic-action sets (default_posture / allow / ask / deny / destructive_class) per the three legs above.
- Locked / Standard ⭐ / Open —
surface: config. Translate to the Claude / Codex config surface. Standard ⭐ is the recommended default: routine dev incl. push allowed, the outward/release/history-rewriting acts (create_pr, publish_package, merge_or_release, force_push) ASK, destructive_class ASKs. Standard has live ask gates → not for a headless-autonomous Claude seat (use Open or YOLO). Locked = deny-default whitelist; Open = allow-default, destructive_class asks only.
- YOLO —
surface: flag, launch_posture: full_bypass. This skill does NOT translate it — it points at OpenRig's deterministic flag-surface opt-in (the rip-out YOLO setting). When YOLO is on, any config-surface policy is MOOT (the bypass overrides it). (Subsumes the former "Operator".)
Built-ins ship read-only in the package (canonical names); copy-to-customize into user space (custom, user-named); the source: frontmatter marker travels on copy. Onboarding presents these as a required MENU (explain each + ask which; no-choice = the floor).
Boundary (keeps this the only maintained piece)
This skill TRANSLATES + APPLIES only. It does not define policies (the policy spec does) and does not enforce at the daemon (OpenRig is agnostic). Spec = what (neutral intent); this skill = how, on this harness version (the caveated, version-stamped translation).
1---2name: applying-a-permission-policy3description: Use when a permission policy is attached to a rig/seat (rig.yaml permission_policy:), or a user asks you to apply one, and you must translate that harness-neutral policy into the target harness's LIVE permission config — Claude settings.json / Codex config.toml / Pi run flags. Agent-driven and interactive: you read the policy, ground yourself in YOUR harness version, show the diff, and ask before writing. NOT for defining policies (that's the policy spec) and NOT a set-and-forget updater.4---56# Applying a permission policy78## Translation is best-effort — read this first9This skill provides the policy schema and OpenRig's best current understanding of how to translate it to each harness. That guidance is **best-effort and version-stamped**: harness permission surfaces change frequently across versions, so a mapping that is correct today can drift on a later release. When you apply a config-surface policy:10- **Verify against the current docs for your installed harness version.** Treat this skill's tables as version-stamped starting points (see `tested_against`), not settled truth.11- **Prefer testing in a protected or disposable environment first.** An incorrect write can lock a seat out or over-permit it, so confirm the format before applying it to a live seat.12- **Hand-editing via the harness's own settings tools is always a valid path.** If a translation is uncertain, setting the policy directly — or asking the user to — is a legitimate outcome, not a failure.1314The reliable, deterministic parts are the **blunt instruments**: the YOLO full-bypass flag and the floor (Claude `acceptEdits` / Codex `workspace-write`), which ride the stable launch-flag surface. **Fine-grained config-rule translation — the allow/ask/deny prefix rules and the Codex posture — is the best-effort part** and carries no guarantee of exact fidelity. If a translation looks wrong, fall back to a blunt instrument or a direct hand-edit.1516## ⚠ Read this preamble first — why this is a SKILL, not a script (do not "improve" it into a deterministic updater)17Harness permission formats are a **moving target**. The exact fact this skill is stamped against — Claude Code 2.1.220 permission rules are **prefix-only** (no flag-precise Bash match) — is *version-specific*; the rule grammar can change on the next point release. A hardcoded/deterministic updater would foot-gun the instant it does, and a wrong permission write can **lock a seat out or silently over-permit it**. So the maintained artifact is this caveated, version-stamped skill that an agent reads and applies **interactively, grounding in the live format** — never a code path that writes blindly. If you are tempted to replace this with a deterministic projector: that temptation is the bug this preamble exists to stop.1819## When to reach for this20- A rig/seat has a policy attached (`rig.yaml … permission_policy: <file>`), and you're at **setup/preflight** — apply it (**mandatory for autonomous seats**, see the freeze warning).21- A user asks "apply the Standard/Open/Locked/Operator policy here" or "set up permissions like <host>".22- NOT for authoring policies (that's the policy spec) and NOT for daemon-level enforcement (OpenRig is agnostic — it injects nothing).2324## What you're translating (the boundary)25INPUT = a **harness-neutral policy spec** (semantic action schema): `default_posture`, `floor`, `allow`/`ask`/`deny` as **semantic actions** (`push_to_remote`, `delete_files`, …), `destructive_class`, `source: builtin|custom`. You TRANSLATE that intent → the harness's live config. You never invent policy; you never pre-bake harness config into the spec.2627## The apply flow (interactive — the interactive-ask IS the product)281. **Read the policy spec** + its `source:` marker (built-in read-only from the package = copy-to-customize before editing; custom = user space).292. **PREFLIGHT (mandatory) — ground yourself in THIS environment:**30 - **daemon-HOME must equal seat-HOME**, or a user-level Claude write never reaches the seat. Check; if they differ, **stop and ask** — do not write into the wrong HOME.31 - Read the harness's **actual current config file** + confirm its schema against your installed version (`claude --version`, the live settings.json shape, codex/pi versions). If the format differs from this skill's `tested_against`, trust the LIVE format and flag the drift.323. **Derive the per-harness config** from the semantic actions (see the three legs below). Compute the Codex posture-collapse + Pi bit.334. **Show the concrete DIFF before writing** (old → new, per file). Never a blind write.345. **Resolve best-effort actions by the built-in's rule FIRST, then ask only on genuine ambiguity.** Best-effort destructive actions (`force_push`, `delete_files`, `delete_everything`, `read_secrets`) default to **`ask`** (never a `deny` that can't be enforced) — apply that default rather than stopping to question-back. Surface a question only when the policy is genuinely ambiguous or the seat is interactive. **For a same-prefix collision (`push_to_remote` vs `force_push`, both `Bash(git push:*)`), NEVER ask — apply the deterministic PREFIX-COLLISION rule in the Claude leg.** On a Claude-only fleet, if the policy denies `network_egress`, **say so explicitly**: it is NOT enforceable — Claude has no network gate, a `Bash(curl:*)`/`Bash(wget:*)` deny is best-effort, and `python`/`node` fetch bypasses it (live-verified: `urllib` reached HTTPS 200 under a curl/wget deny). Route real egress-gating to the Codex sandbox, or record it as advisory on Claude.356. **Back up the current config**, then write. Prefer the **user-level** file (`~/.claude/settings.json`) for durable policy — it's the clobber-resistant tier (deny wins across the union). **ALWAYS include `defaultMode: "acceptEdits"` in the Claude settings.json you write** — the floor key must be PRESENT in the file, or the config policy strips the floor.367. **Verify** the write landed and the seat can see it (re-read; confirm `defaultMode: "acceptEdits"` is present; for an autonomous seat, confirm no `ask` will freeze it).3738## Two surfaces — and this skill writes only ONE of them39Permissions live on two surfaces with opposite stability — which is the whole reason for the agent-driven split:40- **LAUNCH-FLAG surface — STABLE (>1yr) → OpenRig sets it DETERMINISTICALLY, NOT this skill.** The flags OpenRig passes at boot: Claude `--permission-mode` / `--dangerously-skip-permissions`; Codex sandbox/bypass flags; Pi `--approve`/`--no-approve`. **Two things live here, both OpenRig-owned: the FLOOR (minimum, by default) and YOLO (full bypass, opt-in).**41- **CONFIG-FILE surface — CHAOTIC → NEVER deterministic → THIS SKILL'S DOMAIN.** Claude `settings.json` allow/ask/deny + defaultMode; Codex `config.toml` approval rules. This is where the Locked / Standard / Open policies get applied — agent-driven, version-stamped, caveated. (Pi has **no permission surface at all** — its `--approve`/`--no-approve` flags govern project-resource trust, not tool permissions (VM-confirmed Pi 0.83.0); a permission policy does not translate to Pi. See the Pi leg.)4243**THE FLOOR (launch-flag, OpenRig-set — this skill KNOWS it, never WRITES it):** Claude `--permission-mode acceptEdits` (keep it); **Codex workspace-only** (stop forcing `danger-full-access` — ≈ Codex's own default ≈ setting-nothing = agnostic); Pi `--no-approve` (a project-resource-trust floor — Pi has no permission gate; see the Pi leg). **One consistent unconditional minimum**. A chosen config-file policy layers ON TOP of the floor.4445**YOLO MODE (launch-flag, OpenRig-set, opt-in — for people done with permissions):** OpenRig boots every seat with the full-bypass flag (Claude `--dangerously-skip-permissions`; Codex full-bypass). **When YOLO is on, CONFIG-FILE POLICY IS MOOT** — this skill does NOT apply a config policy (or explicitly notes it's overridden by the flag); don't fight the bypass. YOLO is OpenRig's deterministic job, not this skill's. (**Operator = YOLO mode** — subsumed.)4647## The three harness legs (skill-owned mapping knowledge, version-stamped + caveated)4849### Claude — `~/.claude/settings.json` (user-level, clobber-resistant)50- **Action → prefix map** (VM-CONFIRMED @ Claude 2.1.220, 2026-08-03 — every row exercised through real Claude tool calls). `fidelity`: `clean` = the prefix honors intent; `best_effort` = prefix-only can't fully enforce → prefer `ask`, never a `deny` that lies.5152 | Semantic action | `permissions.*` prefix rule(s) | fidelity |53 |---|---|---|54 | push_to_remote | `Bash(git push:*)` | clean |55 | force_push | ask the flag-first forms `Bash(git push --force:*)` `Bash(git push -f:*)` `Bash(git push --force-with-lease:*)`; base `Bash(git push:*)` follows push_to_remote (see the collision rule) | **best_effort** — catches flag-FIRST force; flag-LAST (`git push origin main --force`) leaks |56 | create_pr | `Bash(gh pr create:*)` | clean |57 | publish_package | `Bash(npm publish:*)` `Bash(pnpm publish:*)` `Bash(yarn publish:*)` `Bash(cargo publish:*)` | clean (multi-pattern) |58 | merge_or_release | `Bash(git merge:*)` `Bash(git tag:*)` `Bash(gh release:*)` | best_effort — protected-branch semantics not expressible |59 | delete_files | `Bash(rm:*)` | **best_effort** — target-first leak (`rm <t> -rf` slips); scope not boundable → default `ask` |60 | delete_everything | `Bash(rm -rf:*)` `Bash(rm -fr:*)` | **best_effort** — same leak → default to `ask`, never a silent deny |61 | reset_or_discard_vcs | `Bash(git reset:*)` `Bash(git clean:*)` `Bash(git checkout:*)` `Bash(git branch -D:*)` | best_effort |62 | drop_persistent_store | `Bash(dropdb:*)` `Bash(docker volume rm:*)` (+ project-specific) | best_effort (open-ended) → `ask` |63 | rig_up / rig_down | `Bash(rig up:*)` / `Bash(rig down:*)` | clean — ⚠ an `ask` here FREEZES an autonomous seat |64 | mutate_topology | `Bash(rig add:*)` `Bash(rig remove:*)` `Bash(rig rename:*)` | clean |65 | run_toolchain | `Bash(npm:*)` `Bash(npx:*)` `Bash(pnpm:*)` `Bash(yarn:*)` `Bash(node:*)` `Bash(tsc:*)` `Bash(vitest:*)` `Bash(jest:*)` | clean (the known set) |66 | run_arbitrary_shell | `Bash(*)` — this IS the `default_posture` knob | clean |67 | network_egress | `Bash(curl:*)` `Bash(wget:*)` (partial) | **best_effort** — Claude has NO native network gate; real floor = Codex sandbox (route it there) |68 | read_secrets | `Read(./.env)` `Read(~/.ssh/**)` `Read(**/*secret*)` (path-scoped) | **best_effort** — path-based, not secret-aware |69 | install_dependencies | `Bash(npm install:*)` `Bash(pip install:*)` `Bash(brew install:*)` | clean |70- **PREFIX-ONLY caveat** — cannot flag-precisely gate (`force_push` vs `push_to_remote` both match `Bash(git push:*)`); **target-first leak** (`rm <target> -rf` slips a `Bash(rm -rf*)` deny). Actions marked `best_effort` in the taxonomy (`force_push`, `delete_files`, `read_secrets`) → surface the caveat and prefer `ask` over a `deny` you can't enforce (a `deny` that leaks lies).71- **PREFIX-COLLISION resolution (deterministic — resolve it, NEVER ask).** When two actions collapse to the SAME base prefix with different dispositions — the canonical case is `push_to_remote`=allow + `force_push`=ask, both basing to `Bash(git push:*)` — you cannot put that base prefix in both `allow` and `ask`. Resolve without a question-back:72 - **Push allowed + force asked (Standard / Open):** put the base `Bash(git push:*)` per push's disposition (`allow`), AND add the flag-first force forms to `ask` (`Bash(git push --force:*)`, `Bash(git push -f:*)`, `Bash(git push --force-with-lease:*)`). RECORD in your diff that this is best-effort — flag-LAST force (`git push origin main --force`) leaks past the flag-first ask rules and runs under the allow. Don't block on it.73 - **Push denied (Locked):** `Bash(git push:*)` in `deny` already covers force — no conflict.74 This is a FIXED rule, not a user decision — apply it and note the leak.75- **`defaultMode: "acceptEdits"` is the FLOOR and MUST be written into every Claude settings.json you produce.** It is not a knob you tune, but it IS a key you must always include — a config policy that omits it strips the floor. Live-confirmed schema: `{"permissions":{"allow":[],"ask":[],"deny":[],"defaultMode":"acceptEdits"}}`.7677### Codex — `~/.codex/config.toml` (via the existing `codex_config_fragment` splice path — reuse, don't invent)78- Codex is **per-posture, not per-action**: the whole action set **collapses** to the nearest `{sandbox_mode, approval_policy}` pair. **Built-in → posture** (VM-CONFIRMED @ Codex 0.120.0, 2026-08-03 — each posture applied to a real seat: read-only blocked writes+egress, workspace-write allowed writes/blocked egress, danger-full-access allowed both):7980 | Built-in | `sandbox_mode` | `approval_policy` | Collapse note (surface this to the user) |81 |---|---|---|---|82 | Locked | `read-only` | `on-request` | read-only blocks all writes + egress, prompts on escalation — OVER-restricts `edit_files` vs the Claude floor |83 | Standard ⭐ | `workspace-write` | `on-request` | workspace writes allowed, egress blocked by sandbox, prompt on escalation — UNDER-gates `create_pr` (no per-action ask; folds into the prompt) |84 | Open | `danger-full-access` | `on-failure` | everything allowed, prompt only on failure — OVER-permits the `destructive_class` vs the Claude ask-gates |85 | YOLO | *(flag-surface — full-bypass launch flag, NOT config.toml)* | — | not a config write; the rip-out adapter owns it |8687 Valid values (VM-confirmed @ Codex 0.120.0): `sandbox_mode` ∈ {read-only, workspace-write, danger-full-access}; `approval_policy` ∈ {on-request, on-failure, never, untrusted, granular} (`granular` observed live at 0.120.0). ⚠ `on-failure` (used by Open) is marked **deprecated** in 0.120.0 help but still accepted — revisit if a future Codex drops it.88- Surface the collapse: "Codex can't gate per-action; this policy becomes sandbox=X approval=Y, which over-permits Z / under-permits W."8990### Pi — NO permission surface at all (VM-confirmed Pi 0.83.0 — the earlier "approval bit" reading was WRONG)91- **Pi has no tool-permission gate — neither a config store NOR a permission-posture flag.** Live verification confirmed: Pi 0.83.0's `--approve`/`-a` and `--no-approve`/`-na` govern **project-resource trust** (whether project extensions/resources appear in RPC `get_commands`), **NOT** tool-call approval. There is no sandbox or permission mechanism to map a policy onto.92- So a permission policy **does not translate to Pi**. Do NOT say `--approve` = "auto-approve all", or that a posture "collapses to a Pi approval bit" — that was an earlier misread, corrected here. This skill computes **nothing** for Pi.93- Whatever the OpenRig adapter sets (`--no-approve` by default) is a project-resource-trust floor, not a permission posture — not this skill's to derive.94- Behavior to know (resource-trust, NOT permission-gating): under default ask / `--no-approve`, a project resource is silently **absent** from RPC `get_commands`; it appears under `--approve`. Never mistake that for permission enforcement.95- **Say this to the user for a Pi seat:** "Pi has no permission-policy surface today — the policy applies to Claude/Codex; Pi runs under its project-resource-trust flag unchanged."9697## ⚠ The two warnings that save real fleets98- **`ask` FREEZES an autonomous/headless Claude seat** — it hangs on the first non-edit op. So for autonomous fleets apply **Open/YOLO** (or a policy with no asks) **at setup** — the floor only covers edits; bash still asks without an applied policy. Never leave an autonomous seat on a policy with live `ask` gates. (Pi has no permission ask at all — see the Pi leg; Codex prompts per its posture, not per-action.)99- **daemon-HOME ≠ seat-HOME** silently voids user-level writes — always the preflight check above.100101## The built-ins — DEFINED by the shipped policy spec files (translate FROM the spec; POINT, don't COPY)102The four built-in policies ship as read-only spec files (`source: builtin`, `policy_schema_version: 1`) — they are the AUTHORITATIVE definitions. **Do NOT restate their action-sets here.** The selected spec supplies the action sets; a second copy in this skill can diverge. Read the attached/selected spec and translate its semantic-action sets (`default_posture` / `allow` / `ask` / `deny` / `destructive_class`) per the three legs above.103- **Locked / Standard ⭐ / Open** — `surface: config`. Translate to the Claude / Codex config surface. **Standard ⭐** is the recommended default: routine dev incl. push allowed, the outward/release/history-rewriting acts (`create_pr`, `publish_package`, `merge_or_release`, `force_push`) ASK, destructive_class ASKs. Standard has live `ask` gates → not for a headless-autonomous Claude seat (use Open or YOLO). Locked = deny-default whitelist; Open = allow-default, destructive_class asks only.104- **YOLO** — `surface: flag`, `launch_posture: full_bypass`. This skill does **NOT** translate it — it points at OpenRig's deterministic flag-surface opt-in (the rip-out YOLO setting). When YOLO is on, any config-surface policy is MOOT (the bypass overrides it). (Subsumes the former "Operator".)105106Built-ins ship read-only in the package (canonical names); copy-to-customize into user space (custom, user-named); the `source:` frontmatter marker travels on copy. Onboarding presents these as a required MENU (explain each + ask which; no-choice = the floor).107108## Boundary (keeps this the only maintained piece)109This skill TRANSLATES + APPLIES only. It does **not** define policies (the policy spec does) and does **not** enforce at the daemon (OpenRig is agnostic). Spec = *what* (neutral intent); this skill = *how, on this harness version* (the caveated, version-stamped translation).