agents/openai.yaml
interface:
display_name: "AI Coding Agents — Permissions"
short_description: "Design coding-agent approval systems"
default_prompt: "Use $ai-coding-agents-permissions to design tool approval flows, plan-mode transitions, sandbox prompts, or leader-worker permission routing for a coding-agent runtime."
assets/templates/permission-context.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://skills.ai-coding-agents/permissions/permission-context.schema.json",
"title": "PermissionContext",
"description": "The typed runtime context passed to the permission-decision function when an agent requests a tool call. Represents all facts available at decision time.",
"type": "object",
"required": ["request_id", "session", "tool", "decision_required_by"],
"additionalProperties": false,
"properties": {
"request_id": {
"type": "string",
"description": "Unique ID for this permission request. Used to correlate allow/deny responses with pending requests."
},
"decision_required_by": {
"type": "string",
"format": "date-time",
"description": "Deadline for the decision. If no response by this time, the runtime defaults to 'deny'."
},
"session": {
"type": "object",
"required": ["session_id", "agent_id", "turn"],
"additionalProperties": false,
"description": "Context about the session making the request.",
"properties": {
"session_id": { "type": "string" },
"agent_id": { "type": "string", "description": "ID of the agent definition (e.g. YAML name or .md filename stem)." },
"agent_source": {
"type": "string",
"enum": ["builtin", "project", "user", "plugin", "remote"],
"description": "Origin of the agent definition. 'remote' means the agent was spawned from a remote-runtime or ACP delegation."
},
"turn": { "type": "integer", "minimum": 0 },
"task_id": { "type": ["string", "null"] },
"is_subagent": { "type": "boolean", "description": "True if this session was spawned by an orchestrator agent." },
"parent_session_id": { "type": ["string", "null"] }
}
},
"tool": {
"type": "object",
"required": ["name", "arguments"],
"additionalProperties": false,
"description": "The tool being requested and the arguments it was called with.",
"properties": {
"name": { "type": "string", "description": "Tool name (e.g. 'Bash', 'Edit', 'mcp__github__create_pr')." },
"source": {
"type": "string",
"enum": ["builtin", "mcp", "plugin", "remote"],
"description": "Where the tool is registered."
},
"arguments": {
"type": "object",
"description": "Raw arguments object passed to the tool. Structure is tool-specific.",
"additionalProperties": true
},
"estimated_side_effects": {
"type": "array",
"items": {
"type": "string",
"enum": ["file_read", "file_write", "file_delete", "process_exec", "network_outbound", "network_inbound", "env_read", "env_write"]
},
"description": "Side-effect categories predicted by static analysis of the tool and arguments. Advisory only."
},
"is_destructive": {
"type": "boolean",
"description": "True if the tool is classified as destructive (delete, overwrite, exec with side effects)."
}
}
},
"policy_match": {
"type": "object",
"additionalProperties": false,
"description": "The policy rule that matched this request, if any. Null means no explicit rule; default behavior applies.",
"properties": {
"rule_id": { "type": "string" },
"rule_source": {
"type": "string",
"enum": ["builtin_default", "project_settings", "user_settings", "managed_policy"]
},
"decision": {
"type": "string",
"enum": ["allow", "ask", "deny"]
},
"matched_pattern": {
"type": "string",
"description": "The glob or regex pattern in the rule that matched this tool call."
}
}
},
"approval_context": {
"type": "object",
"additionalProperties": false,
"description": "Additional context to surface in the approval UI when decision = 'ask'.",
"properties": {
"display_tool_name": { "type": "string" },
"display_arguments_summary": { "type": "string", "description": "Human-readable summary of the arguments (not the raw object)." },
"risk_label": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "Risk label to show in the approval prompt."
},
"suggested_decision": {
"type": "string",
"enum": ["allow", "deny"],
"description": "The runtime's suggested decision based on policy and heuristics."
}
}
}
},
"examples": [
{
"request_id": "perm_01j9kx2fvg3b4h7r",
"decision_required_by": "2026-04-27T14:22:31.000Z",
"session": {
"session_id": "sess_01j9kx2fvg3b4h7r",
"agent_id": "code-reviewer",
"agent_source": "project",
"turn": 3,
"task_id": null,
"is_subagent": false,
"parent_session_id": null
},
"tool": {
"name": "Bash",
"source": "builtin",
"arguments": { "command": "rm -rf dist/" },
"estimated_side_effects": ["process_exec", "file_delete"],
"is_destructive": true
},
"policy_match": {
"rule_id": "destructive-bash-ask",
"rule_source": "project_settings",
"decision": "ask",
"matched_pattern": "Bash(rm *)"
},
"approval_context": {
"display_tool_name": "Bash",
"display_arguments_summary": "Delete 'dist/' directory recursively",
"risk_label": "high",
"suggested_decision": "deny"
}
}
]
}
data/sources.json
{
"metadata": {
"skill": "ai-coding-agents-permissions",
"title": "AI Coding Agents Permissions - Sources",
"description": "Official documentation and implementation references for coding-agent approval systems, permission modes, and delegated permission routing",
"last_updated": "2026-07-11",
"updated": "2026-07-11",
"total_sources": 15,
"version": "1.1"
},
"categories": {
"official_documentation": [
{
"name": "Claude Code Documentation",
"url": "https://code.claude.com/docs/en",
"type": "documentation",
"relevance": "Primary product documentation for runtime behavior and approval surfaces",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Permission Modes",
"url": "https://code.claude.com/docs/en/permission-modes",
"type": "reference",
"relevance": "All six permission modes (default, acceptEdits, plan, auto, dontAsk, bypassPermissions), auto mode classifier detail, Shift+Tab cycling, disableAutoMode, plugin subagent restrictions",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Anthropic Engineering: Claude Code Auto Mode",
"url": "https://www.anthropic.com/engineering/claude-code-auto-mode",
"type": "engineering_post",
"relevance": "Deep dive on auto mode two-stage classifier architecture, transcript stripping against prompt injection, false positive/negative rates",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Hooks Documentation",
"url": "https://code.claude.com/docs/en/hooks",
"type": "guide",
"relevance": "Hook and event model for automated checks that feed approval decisions, including the `if` permission-rule scoping field on hook handlers and its fail-open behavior",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Permissions Reference",
"url": "https://code.claude.com/docs/en/permissions",
"type": "reference",
"relevance": "Full permission rule syntax: evaluation order (deny > ask > allow, first match wins), tool-name and parameter-value (`Tool(param:value)`) rules, Agent/MCP/Read/Edit/WebFetch/Cd specifiers, protected paths, managed-only settings, and settings precedence for permission rules",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Settings Documentation",
"url": "https://code.claude.com/docs/en/settings",
"type": "reference",
"relevance": "Settings precedence order (managed > CLI args > local project > shared project > user) that permission-rule audits must reason about alongside deny-wins-regardless-of-scope semantics",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
}
],
"implementation_references": [
{
"name": "Claude Code GitHub Repository",
"url": "https://github.com/anthropics/claude-code",
"type": "repository",
"relevance": "Closed-source product page and issue tracker; not the implementation source — use code.claude.com/docs/en for authoritative behavior docs",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false
},
{
"name": "Anthropic: Building Effective Agents",
"url": "https://www.anthropic.com/engineering/building-effective-agents",
"type": "guide",
"relevance": "High-level guidance on agent control boundaries and reliable tool use",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"cross_runtime_references": [
{
"name": "Codex CLI Documentation",
"url": "https://github.com/openai/codex",
"type": "documentation",
"relevance": "Cross-runtime comparison point for sandbox and approval behavior",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Split Permissions Source",
"url": "https://github.com/openai/codex/blob/main/codex-rs/protocol/src/permissions.rs",
"type": "repository_source",
"relevance": "First-party source for split filesystem policy, read/write/deny access, network policy, protected metadata names (`.git`, `.agents`, `.codex` — re-verified against `main` 2026-07-11, unchanged), and sandbox capability modeling. Prefer `main` over a pinned commit for this fast-moving config surface (see AskForApproval enum drift note in SKILL.md)",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Execpolicy README",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/execpolicy/README.md",
"type": "repository_source",
"relevance": "Pinned first-party source for prefix-rule policy evaluation, strictest-decision wins, examples as tests, host_executable matching, and structured policy output. Content re-checked against `main` 2026-07-11 (allow/prompt/forbidden decisions, host_executable fallback rules) with no material drift",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Network Proxy Config Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/network-proxy/src/config.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for domain allow/deny config, local binding policy, unix socket permissions, and MITM proxy settings",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Network Connect Policy Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/network-proxy/src/connect_policy.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for network destination checks, deny precedence, loopback/private target handling, and local binding enforcement",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "Model Context Protocol Specification",
"url": "https://modelcontextprotocol.io/",
"type": "specification",
"relevance": "Reference for tool and server capability boundaries when approvals cross tool or transport layers",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
]
}
}
learnings.consolidated.md
# ai-coding-agents-permissions — Consolidated Learnings
Curated, dated, committed memory for this skill. Pruned from raw `learnings.md` via `agents-skills-feedback-loop/scripts/consolidate.py`. Human-approved.
Cap: 60 entries. When exceeded, promote durable rules to `references/`.
## Filter Override
<!-- Add 2-4 bullets that sharpen what counts as a learning for this skill. Leave empty to use the default filter from agents-skills-feedback-loop/references/learnings-format.md. -->
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
## Open Questions
## Consolidated Principles
learnings.md
# ai-coding-agents-permissions — Learnings
## Patterns That Work
- [2026-07-11] Audit fix for over-permissive local setups: switch `defaultMode: "bypassPermissions"` → `"auto"` and delete blanket `Bash(*)`/bare-tool allows; keep specific prefix rules (`Bash(npm run build:*)`) so routine commands stay frictionless while the classifier gates the rest. Applied across 7 repos with no day-to-day workflow change.
- [2026-07-11] When auditing settings for embedded secrets, grep permission files for token shapes (`eyJ` JWT, `Bearer [A-Za-z0-9._-]{30,}`, `sk-`, `ghp_`, `AIza`, `xox[bap]-`) — "always allow" clicks persist full command strings, including auth headers, into `.claude/settings.local.json`.
## Mistakes to Avoid
- [2026-08-14] Adding an HTTP MCP server with Codex may immediately start OAuth; when the intended resting state is logged out, follow registration with an explicit logout and verify only non-secret config keys.
- [2026-07-11] A bare `Bash` (or `Bash(*)`) allow rule silently disables the auto-mode classifier for all shell commands — allow rules bypass classification unless `autoMode.classifyAllShell: true`. Blanket Bash allow + auto mode gives bypass-level exposure while looking safe.
- [2026-07-11] Approving a raw `curl` with an `Authorization: Bearer <token>` header via "always allow" wrote the live token into `settings.local.json`, where it sat in plaintext (and in transcripts) for months. Secrets belong in `.env.local`; never persist an allow rule containing a credential — and rotate any token found this way.
- [2026-07-11] With `defaultMode: "bypassPermissions"`, the entire allow/ask/deny structure is decorative — carefully curated `ask` lists for `rm -rf`/`git push --force`/`DROP` enforce nothing. Verify the mode before trusting any rule list during an audit.
## Domain Knowledge
- [2026-07-11] Settings precedence is managed > CLI > local project > shared project > user, but a deny rule from any scope blocks regardless of that precedence order — deny beats allow at every scope.
- [2026-07-11] Claude Code v2.1.186+ has background subagents escalate denied-by-default tool calls to the parent session (labeled with subagent name) instead of silently auto-denying them.
- [2026-07-11] Codex AskForApproval drifted since a May-2026 pinned source: UnlessTrusted now serializes "untrusted" (not "unless-trusted"); standalone OnFailure is gone, "on-failure" is now just a serde alias for OnRequest.
- [2026-07-11] Claude Code allow rules reject wildcard tool names: `mcp__*` is invalid and skipped with a startup warning. An MCP allow rule must name a literal server (`mcp__<server>` or `mcp__<server>__toolglob*`); wildcards anywhere are only legal in deny and ask rules.
- [2026-07-11] Permission precedence is deny > ask > allow, so `ask` entries still gate commands matched by broader allow rules (in modes that consult rules at all).
- [2026-07-11] Absolute paths in Read/Edit rules use a double-slash prefix (`Read(//Users/x/**)`); a single leading slash is resolved relative to the settings file's directory, so `Read(/Users/x/**)` in user settings does not match what it appears to.
## Open Questions
## Consolidated Principles
references/hostile-path-test-checklist.md
# Hostile-Path Test Checklist
Scenarios to exercise before shipping any permission subsystem. Cover denied paths, user cancellations, remote-unknown-tool calls, and bypass-mode invariants. Each row includes a Resolution to make this a generative toolkit, not just a list.
---
## How to Use
Mark each row `pass`, `fail`, or `skip+reason` before release. A permission subsystem is not shippable until all non-skipped rows are `pass`. Re-run after changes to policy rules, approval UI, or tool-registration logic.
---
## Section 1 — Denied Requests
| ID | Scenario | Setup | Expected Behavior | Resolution if Failing |
|----|----------|-------|-------------------|-----------------------|
| DENY-01 | Tool in explicit deny list | Policy has `Bash` → `deny` | Tool call returns `PermissionDenied` immediately; no approval prompt shown | Check that `deny` rules are evaluated before `ask` rules in the policy evaluation order |
| DENY-02 | Subagent requests tool denied by managed policy | Managed policy denies `file_delete` for all subagents | `PermissionDenied` returned; parent agent is notified via `permission.denied` event | Managed policy must be applied at the outermost decision point, not just the agent-level check |
| DENY-03 | Destructive Bash command without explicit allow | `rm -rf` issued without a prior `allow` rule | Tool call blocked; approval prompt shown (or denied if `ask` not in policy) | Classify destructive patterns at argument-parse time; do not rely on model self-assessment |
| DENY-04 | Network tool call when network is disabled | Agent calls an MCP network tool with `allow_network = false` | Denied before the tool handler fires | Network-disabled flag must be checked in the permission layer, not in the tool handler |
| DENY-05 | Deny rule overrides a broader allow rule | `allow: Bash(*)` + `deny: Bash(rm *)` | `rm` variant denied; all other Bash calls allowed | Rule evaluation must be: specific deny > specific allow > broad allow > default |
---
## Section 2 — User Cancellation
| ID | Scenario | Setup | Expected Behavior | Resolution if Failing |
|----|----------|-------|-------------------|-----------------------|
| CANCEL-01 | User dismisses approval prompt without choosing | Approval prompt appears; user closes it | Tool call treated as `deny`; no side effects; agent notified of cancellation | Timeout or close event on the approval UI must resolve the pending promise as `deny`, never as `allow` |
| CANCEL-02 | User cancels mid-session while approval prompt is open | User presses Ctrl+C while an `ask` prompt is pending | Session cancelled; pending approval resolved as `deny`; `task.cancelled` event emitted | Cancellation signal must drain the approval queue with `deny` before shutting down |
| CANCEL-03 | User cancels a previously-allowed tool before execution | User allowed a Bash command; then cancels before the process forks | Process never started; `task.cancelled` event emitted | Allow decisions must not be considered irrevocable until the tool handler receives control; insert a cancellation checkpoint between allow and exec |
| CANCEL-04 | Repeated cancellations do not leave zombie approvals | User cancels 5 times in a row | Each cancellation results in a clean `deny`; no leaked approval state | Approval queue must be fully drained and reset on each cancellation |
---
## Section 3 — Remote / Unknown Tool
| ID | Scenario | Setup | Expected Behavior | Resolution if Failing |
|----|----------|-------|-------------------|-----------------------|
| REM-01 | Agent requests a tool not in the local registry | Remote agent calls `mcp__unknown_server__do_thing` | Permission layer returns `ToolNotRegistered`; agent shown an informative error | Unknown-tool check must fire before policy evaluation; do not evaluate policy for unregistered tools |
| REM-02 | MCP server registers a tool whose name collides with a builtin | Plugin registers a tool named `Bash` | Rejected at registration time; existing `Bash` binding preserved | Tool registration must check for name collisions and reject or namespace the new entry |
| REM-03 | Remote-runtime tool call arrives with no session context | Tool call received over WebSocket with no `session_id` | Rejected with `AuthRequired`; not evaluated against policy | Session context must be validated before policy evaluation; unauthenticated requests never reach the policy layer |
| REM-04 | ACP-delegated subagent requests a tool outside its declared scope | Subagent's manifest lists `allowed_tools: [Read, Grep]`; it calls `Bash` | Denied at the orchestrator's permission layer; `permission.denied` event forwarded over ACP | Orchestrator must re-apply its own policy to all ACP-delegated tool calls; the delegated agent's allow-list is an upper bound, not a bypass |
| REM-05 | Tool response arrives after session expires | Tool was dispatched, session expired before response | Response discarded; `session.expired` event emitted; no state mutation applied | Implement a session-validity check on tool-response ingestion, not only on tool dispatch |
---
## Section 4 — Bypass Mode
| ID | Scenario | Setup | Expected Behavior | Resolution if Failing |
|----|----------|-------|-------------------|-----------------------|
| BYP-01 | Bypass mode enabled allows all non-destructive calls | `bypassPermissions: true` in dev config | Non-destructive tool calls proceed without approval prompt | Bypass mode must only suppress the prompt, never mutate policy state; destructive-class tools must still be blocked unless explicitly added to the allow list |
| BYP-02 | Bypass mode does NOT bypass deny rules | `bypassPermissions: true` + `deny: file_delete` | `file_delete` still denied despite bypass mode | `deny` rules are unconditional; bypass mode only affects `ask` behavior, not `deny` behavior |
| BYP-03 | Bypass mode is never active in production config | Managed policy sets `bypass_mode: prohibited` | Dev flag ignored when managed policy is present | Managed policy enforcement must gate on the full policy chain, including a `bypass_mode: prohibited` check |
| BYP-04 | Audit log entries are not suppressed in bypass mode | Bypass mode active; agent makes 10 tool calls | All 10 calls appear in the audit log with `bypass_mode: true` annotation | Bypass mode suppresses approval prompts, never audit events; verify the audit pipeline is independent of the approval path |
| BYP-05 | Subagents do not inherit parent bypass mode | Parent session has bypass mode; spawns a subagent | Subagent evaluates its own policy; bypass mode does not propagate | Session-scope flags must not be inherited by child sessions unless the child's own config explicitly sets them |
---
## Pass Criteria
A permission subsystem passes this checklist when:
- All `DENY-*` rows produce the documented `PermissionDenied` result with no side effects.
- All `CANCEL-*` rows produce clean `deny` outcomes with no leaked state.
- All `REM-*` rows reject or error before any side-effecting code runs.
- All `BYP-*` rows confirm that bypass mode is scoped, audited, and cannot override `deny` rules.
references/openai-codex-execpolicy-and-network-proxy.md
# OpenAI Codex Execpolicy and Network Proxy
Sources:
- OpenAI Codex repo, commit `9f42c89c0112771dc29100a6f3fc904049b2655f`
- `codex-rs/execpolicy/README.md`
- `codex-rs/network-proxy/src/config.rs`
- `codex-rs/network-proxy/src/connect_policy.rs`
Use this reference when designing allow/ask/deny rule engines, shell-prefix approvals, network sandbox policy, or policy explainability for coding-agent runtimes.
## Table of Contents
- [What To Steal](#what-to-steal)
- [Network Proxy Boundary](#network-proxy-boundary)
- [Portable Permission Contract](#portable-permission-contract)
- [Tests To Require](#tests-to-require)
- [Source Links](#source-links)
## What To Steal
### Prefix rules as executable policy
Codex has a policy language around prefix rules. A rule matches command prefixes and returns a decision with justification.
Reusable shape:
```text
prefix_rule(
pattern,
decision,
justification,
match?,
not_match?
)
```
The important design choice is that policy is executable and testable, not a loose list of strings. Rules can be evaluated with structured output, and examples can be used as load-time tests.
Design rule:
- Store shell rules in a policy file or policy object.
- Evaluate command requests through the same engine every time.
- Emit machine-readable decision, matching rule, and justification.
- Treat policy examples as tests.
Known trap:
- Persisting raw command prefixes without a policy evaluator makes it hard to detect shadowed, unreachable, or overbroad approvals.
### Strictest decision wins
Codex's execpolicy docs describe a strictest-decision model. That is the right default when allow, ask, and deny rules overlap.
Recommended precedence:
1. deny
2. ask
3. allow
This prevents a broad allow from accidentally overriding a narrower deny.
Known trap:
- First-match-wins is easy to implement but fragile. Rule order becomes a hidden security boundary.
### Host executable helper
Codex includes a `host_executable` helper for matching the executable part of a command. Import the idea, but constrain it tightly.
Safe use:
- Match the canonical executable token after parsing.
- Avoid matching arbitrary substrings.
- Keep fallback basename matching explicit and explainable.
Known trap:
- A rule like "allow anything containing npm" is not equivalent to "allow host executable npm." String matching turns policy into guesswork.
## Network Proxy Boundary
Codex's network-proxy config separates network policy from tool approval. Useful patterns:
- Domain allow and deny lists.
- Deny precedence over allow.
- Local binding disabled by default.
- Loopback and non-public targets guarded when local binding is disabled.
- Unix socket permissions separated from domain policy.
- MITM hooks modeled as explicit proxy behavior, not hidden tool behavior.
This belongs with permissions because network reachability is a capability boundary, even when the shell command was approved.
Design rule:
- Approval to run a tool is not approval for arbitrary network egress.
- Network destination policy should be evaluated independently from command policy.
- Log blocked destination and policy source separately from shell approval state.
Known trap:
- Treating `network_access=true` as one global boolean loses the difference between "can fetch package registries" and "can connect to local services or private IPs."
## Portable Permission Contract
```text
PermissionDecision
decision: allow | ask | deny
source: managed | repo | user | session
matched_rule_id
justification
normalized_command?
network_destination?
policy_layer
```
Policy load should fail or warn on:
- unreachable rules
- deny rules shadowed by broader allow rules if using first-match semantics
- broad shell prefixes without parsed executable anchors
- network allow lists without deny precedence
- local binding enabled without explicit user or org policy
## Tests To Require
- A narrower deny beats a broader allow.
- A narrower ask beats a broader allow.
- Rule examples pass at policy load time.
- Policy evaluation emits JSON or equivalent structured output.
- `host_executable` style matching does not match arbitrary substrings.
- Domain deny beats domain allow.
- Loopback/private targets are blocked when local binding is disabled.
- Network block is reported as network policy, not shell denial.
## Source Links
- [execpolicy README](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/execpolicy/README.md)
- [network proxy config](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/network-proxy/src/config.rs)
- [network connect policy](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/network-proxy/src/connect_policy.rs)
references/openai-codex-request-permissions-and-split-policy.md
# OpenAI Codex Request Permissions And Split Policy
Source snapshot: OpenAI Codex commit `7d47056ea42636271ac020b86347fbbef49490aa` (2026-05-22), especially `codex-rs/protocol/src/permissions.rs`, `codex-rs/core/src/tools/handlers/shell_spec.rs`, and `codex-rs/core/README.md`.
## Table Of Contents
- [Design Goal](#design-goal)
- [Split Filesystem Policy](#split-filesystem-policy)
- [Protected Metadata](#protected-metadata)
- [Permission Request Tool](#permission-request-tool)
- [Approval Policy Is Not Sandbox Policy](#approval-policy-is-not-sandbox-policy)
- [Test Matrix](#test-matrix)
## Design Goal
Use a capability policy that can express exact filesystem and network permissions, then route approval prompts through the host. Older `read-only` / `workspace-write` / `danger-full-access` modes are useful presets, but they are too coarse as the only internal model.
## Split Filesystem Policy
Codex models filesystem access as entries with:
- path: concrete path or special path such as root, project roots, temp directory, or platform defaults
- access: `read`, `write`, or `deny`
- kind: restricted, unrestricted, or external sandbox
This lets a runtime express cases that coarse sandbox modes cannot:
- writable project root with read-only or denied carveouts
- denied child under a writable parent
- writable child reopened under a denied parent
- restricted read roots on platforms that can enforce them
For new runtimes, normalize high-level presets into this lower-level model before execution.
## Protected Metadata
Codex protects top-level workspace metadata names under writable roots:
- `.git`
- `.agents`
- `.codex`
Use this as a default rule. A workspace-write sandbox should not imply that the agent can rewrite repository metadata, local agent definitions, or runtime policy files unless there is an explicit write grant for that metadata path.
## Permission Request Tool
Codex exposes a `request_permissions` tool that asks for a structured permission profile rather than forcing every command to request full escalation. Granted permissions can apply to later shell-like commands in the current turn or, if approved at session scope, for the rest of the session.
Copy the pattern:
- prefer requesting narrower filesystem or network permissions
- keep full unsandboxed escalation as the exception
- distinguish fresh requests from already preapproved sticky grants
- record whether a grant is turn-scoped or session-scoped
## Approval Policy Is Not Sandbox Policy
Codex keeps approval behavior and sandbox enforcement separate. An approval policy can suppress prompts, but it does not by itself create filesystem or network authority.
Design rule:
- approval policy answers "may the runtime ask or auto-decide?"
- sandbox policy answers "what can the process actually access?"
- permission grants are explicit changes to sandbox capability, not merely approval state
## Test Matrix
Codex's source and sandbox smoke tests point to the hostile cases worth copying:
- write inside workspace succeeds only when intended
- write outside workspace fails unless explicitly granted
- protected metadata remains read-only by default
- symlink and junction paths cannot bypass carveouts
- malformed deny globs fail closed
- network behavior follows network policy, not command text
- platform fallbacks fail closed when exact policy cannot be enforced
## Traps
- Treating `approval_policy = never` as permission to bypass sandboxing.
- Making `.git`, `.agents`, or `.codex` writable just because the repo root is writable.
- Falling back to a weaker sandbox silently when a split policy cannot be enforced.
- Asking for unsandboxed escalation when a narrower additional permission would work.
references/permission-routing-local-remote-and-worker.md
# Permission Routing: Local, Remote, And Worker Flows
## Table Of Contents
- [Design Goal](#design-goal)
- [Local Interactive Flow](#local-interactive-flow)
- [Remote Session Flow](#remote-session-flow)
- [Swarm Worker Flow](#swarm-worker-flow)
- [Unknown Tool And Synthetic Request Handling](#unknown-tool-and-synthetic-request-handling)
## Design Goal
Approval semantics should stay the same across execution topologies, but transport should differ. The `claude_code` source shows three distinct routing paths:
- local REPL approval
- remote session approval
- worker-to-leader approval
## Local Interactive Flow
Local interactive approval is the simplest path:
- tool requests enter the host permission system
- automated checks can run before dialog display
- the user approves, rejects, or updates input
- the host returns a structured result to the tool executor
This is the baseline all other approval paths should emulate semantically.
## Remote Session Flow
`RemoteSessionManager.ts` handles a remote approval path:
- the remote server sends a control request for `can_use_tool`
- the local client stores the pending request by request ID
- the client surfaces the approval UI locally
- the approval result is sent back as a structured remote permission response
This means the UI and the tool execution can live on different machines while sharing one approval contract.
## Swarm Worker Flow
`useSwarmPermissionPoller.ts` shows the worker flow:
- a worker registers a pending callback keyed by request ID
- the worker polls for a leader response
- mailbox or disk-backed updates are validated before callbacks fire
- sandbox permission responses use a parallel callback registry
The architectural lesson:
- worker approvals need durable identifiers
- leader responses should survive process or render boundaries
- malformed external approval updates must be filtered before execution resumes
## Unknown Tool And Synthetic Request Handling
`remotePermissionBridge.ts` adds two useful fallback patterns:
- create a synthetic assistant message when remote tool use has no local message object
- create a stub tool object when the remote server exposes a tool the local client does not know
Copy these rules:
- remote approval should not fail just because the local client lacks the full tool implementation
- the host should be able to render and decide on a request using normalized synthetic wrappers
- provenance should remain explicit so users know the tool originated remotely
references/permission-runtime-model.md
# Permission Runtime Model
## Table Of Contents
- [Design Goal](#design-goal)
- [Central Permission Context](#central-permission-context)
- [Mode Handling](#mode-handling)
- [Tool vs Host Responsibility](#tool-vs-host-responsibility)
- [Promptability Rules](#promptability-rules)
## Design Goal
Coding-agent runtimes need one host-owned permission model that stays consistent across tools, sessions, and execution topologies. Do not let each tool invent its own approval semantics.
The `claude_code` source makes this explicit through `ToolPermissionContext` in `Tool.ts`.
## Central Permission Context
`ToolPermissionContext` holds the approval state for a running session:
- `mode`
- allow, deny, and ask rules by source
- additional working directories
- bypass and auto-mode availability
- stripped dangerous rules
- background-agent promptability flags
- a `prePlanMode` field so plan-mode transitions can restore the prior mode
That is the correct model to copy:
- keep one canonical permission object
- let multiple runtime layers read it
- update it in place through host-owned transitions
## Mode Handling
The source shows that plan mode is not just a UI state. It is part of permission control:
- the runtime stores the pre-plan permission mode
- model-initiated plan-mode entry can temporarily change behavior
- exit restores the previous mode instead of guessing
Use the same rule:
- permission mode transitions should be explicit and reversible
- plan mode should not permanently corrupt ordinary approval state
## Tool vs Host Responsibility
The host owns:
- rule precedence
- allow/deny/ask semantics
- when prompts are shown
- whether a request can be delegated or auto-denied
Tools can still contribute:
- human-readable request text
- tool-specific input rendering
- tool-specific validation of whether approval is needed
Do not let tools store the final approval policy themselves.
## Promptability Rules
`Tool.ts` also captures a critical distinction:
- some contexts should avoid prompts entirely
- some contexts should await automated checks before showing a dialog
Those two flags matter for production coding agents:
- background workers cannot safely block forever on a UI they do not control
- coordinator workers may need classifier or hook output before a final dialog is shown
Treat “can prompt the user” as a first-class runtime capability, not an assumption.
## Edge Cases And Workarounds
Production permission systems need a few more rules than the high-level model suggests:
- over-broad shell or Bash allow rules
- sanitize or strip them after loading from disk
- do not trust user-edited permission rules to be safe just because they parsed
- bypass mode availability
- represent "bypass exists" separately from "bypass is currently allowed"
- policy or runtime state may disable bypass without removing the field from the model
- background workers
- if they cannot render approval UI, auto-deny or route the request to a controller
- do not leave them waiting forever on a prompt no one can see
- plan-mode entry and exit
- store the pre-plan mode explicitly
- exiting plan mode should restore, not recompute, the old mode
- automated checks before dialog
- if approval depends on hooks or classifiers, record that the dialog is intentionally delayed
Practical rule:
- keep the permission object small enough to reason about
- put every weird approval branch behind explicit booleans or enums
- avoid hidden behavior inferred from UI state alone
SKILL.md
---
name: ai-coding-agents-permissions
description: "Designs approval and permission systems for coding-agent runtimes. Use when modeling tool approvals, plan-mode transitions, sandbox prompts, or worker permission handoffs."
compatibility: Portable core. Works on Claude Code and Codex.
version: "1.1"
last_validated: 2026-07-11
---
# AI Coding Agents Permissions
Use this skill to design or review the approval system for a coding-agent runtime: tool permission modes, plan-mode entry and exit, sandbox escalation, background-agent auto-deny behavior, and leader-worker permission routing.
This skill owns approval architecture for coding agents. For general hooks or callback automation, use [`../agents-hooks/SKILL.md`](../agents-hooks/SKILL.md).
## ASCII Flow
```text
tool/action request
|
v
permission context
mode + actor + sandbox + tool + path + remote/local + worker role
|
v
policy route
allowlist | ask rule | deny rule | managed policy | plan-mode gate
|
v
decision
allow -> execute
ask -> prompt owner or lead session
deny -> return structured refusal
log -> trace permission reason
```
## Quick Reference
| Question | Read | Outcome |
|----------|------|---------|
| How should permission state live in the runtime? | [`references/permission-runtime-model.md`](references/permission-runtime-model.md) | Central permission context, mode handling, and host-owned rules |
| How do local, remote, and swarm approvals differ? | [`references/permission-routing-local-remote-and-worker.md`](references/permission-routing-local-remote-and-worker.md) | Approval flows for REPL, remote sessions, and leader-worker teams |
| How does OpenAI Codex model split filesystem policy and `request_permissions`? | [`references/openai-codex-request-permissions-and-split-policy.md`](references/openai-codex-request-permissions-and-split-policy.md) | Read/write/deny entries, protected metadata, scoped permission grants, and sandbox-vs-approval separation |
| How does OpenAI Codex structure executable policy and network egress policy? | [`references/openai-codex-execpolicy-and-network-proxy.md`](references/openai-codex-execpolicy-and-network-proxy.md) | Prefix-rule evaluation, strictest-decision wins, structured justifications, network deny precedence, and local binding controls |
## When To Use
- Design a permission model for a coding-agent CLI or runtime
- Add tool approval prompts, ask/allow/deny rules, or sandbox escalation
- Model plan-mode entry and exit as part of the permission system
- Route worker approvals through a lead agent or remote bridge
- Decide what background agents should auto-deny instead of prompting for
## Use Other Skills
| Need | Use Instead |
|------|-------------|
| Hook automation and lifecycle callbacks | [`../agents-hooks/SKILL.md`](../agents-hooks/SKILL.md) |
| Plugin trust and install-time capability boundaries | [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md) |
| Session resume and transcript recovery | [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md) |
| Multi-agent worker coordination | [`../agents-swarm-orchestration/SKILL.md`](../agents-swarm-orchestration/SKILL.md) |
| Full settings-source precedence, managed policy layering, env controls | [`../ai-coding-agents-settings-policy/SKILL.md`](../ai-coding-agents-settings-policy/SKILL.md) |
## Default Workflow
1. **Model permission as runtime state, not scattered booleans.** Keep a single host-owned permission context with mode, rule sources, and special-case flags.
2. **Separate policy layers.** Distinguish always-allow, always-deny, always-ask, org policy, and session-local overrides.
3. **Define promptability.** Background workers, headless sessions, or remote viewers may need auto-deny or delegated approval instead of local dialogs.
4. **Treat plan mode as a permission transition.** Entering or exiting plan mode should preserve the prior mode so the runtime can safely restore it.
5. **Route approvals by execution topology.** Local REPL, remote session, and swarm worker flows should share semantics but not transport.
6. **Keep pending approvals as first-class runtime objects.** Prompt IDs, tool-use IDs, cancellation state, and approval outcomes should be tracked explicitly so remote cancellation and reconnect behavior cannot desynchronize the UI.
7. **Bridge remote requests into local renderables.** Remote approval prompts may reference tools unknown to the local client; normalize them through synthetic assistant messages or tool stubs so the UI can still explain what is being approved.
8. **Keep tool-specific rendering separate from host policy.** A tool can explain its request, but the host decides how approval is enforced and remembered.
9. **Lint persisted rules.** Detect unreachable or shadowed allow rules before they enter the active policy set.
10. **Test hostile paths.** Verify denied prompts, cancelled prompts, remote unknown-tool approvals, worker poll timeouts, bypass-mode restrictions, and over-broad shell rules.
## Host Rules
- Keep one canonical permission context for the session.
- Let tools contribute request detail, not the final allow/deny policy.
- Background agents that cannot show UI should auto-deny or escalate to a controller instead of hanging.
- Await automated checks before showing dialogs when coordinator workers depend on classifier or hook output.
- Remote and worker approval flows should return structured approval results, not implicit UI side effects.
- Permission prompts should have stable request IDs and explicit cancellation behavior.
- Unknown remote tools should still be approvable through synthetic local rendering paths rather than becoming unrenderable errors.
- Over-broad persisted shell rules should be sanitized or refused before they are admitted into the active permission context.
- Record enough metadata to explain why a request was allowed, denied, or never prompted.
- Shared policy sources and personal policy sources may need different sanitization or auto-allow behavior. Preserve source class through evaluation.
## Build Order
1. Define one canonical permission context for the host runtime.
2. Model permission modes and rule sources explicitly.
3. Implement request IDs, approval results, and cancellation semantics.
4. Route approval by topology: local, remote, worker, or non-interactive.
5. Add persistence or session-local memory for approved rules.
6. Add sanitization and shadowed-rule detection for dangerous or over-broad rule proposals.
7. Add source-aware handling for shared versus personal policy layers.
## Core Invariants
- The host owns allow, deny, ask, and persistence policy.
- Tools describe requests; they do not decide approval policy.
- Non-interactive actors must never hang waiting for a prompt they cannot answer.
- Every prompt must end in exactly one terminal outcome: approved, denied, cancelled, or expired.
- Plan mode is a reversible permission transition, not a separate permission system.
- Persisted rules must be linted for reachability and dangerous breadth before activation.
## Failure Modes
- Prompt IDs that cannot be matched to cancellations or late results.
- Remote approvals that reference unknown tools and therefore become unrenderable.
- Persisted shell rules that silently become broader than intended.
- Workers blocking forever while waiting for interactive approval.
- Plan-mode exit failing to restore the preexisting permission state.
- Lower-precedence allow rules that are unreachable because an earlier ask or deny rule shadows them.
## Minimal Viable Version
- One session-owned permission context.
- Explicit ask, allow, and deny modes.
- Stable IDs for prompts and approval results.
- Auto-deny for non-interactive workers.
- One sanitization or lint pass over persisted rules before activation.
- Approval metadata that explains why a request was allowed or denied.
## What Strong Implementations Add
- Policy layering across org, repo, session, and ephemeral overrides.
- Automated checks before prompting the user.
- Synthetic local rendering for remote approval prompts.
- Cancellation, expiry, and retry-safe bookkeeping.
- Rule sanitization and explainability for persisted approvals.
- Shadowed-rule detection and source-aware behavior differences for shared versus personal policy layers.
## Known Traps
- Letting multiple subsystems infer approval state independently and then diverge between the runtime, UI, and stored policy.
- Persisting shell-prefix approvals without enough narrowing to prevent future privilege creep.
- Treating remote approval as a rendering problem instead of a durable runtime object with expiry, cancellation, and retry semantics.
- Recording “prompt shown” as if it were equivalent to a valid approval outcome.
- Allowing tools or plugins to own their own approval policy and bypass central auditability.
## Common Anti-Patterns
- Storing approval mode as scattered booleans instead of one context object.
- Letting tools persist their own approval policy.
- Treating remote approval as a UI problem instead of a runtime-routing problem.
- Persisting arbitrary shell prefixes without narrowing or review.
- Assuming every stored allow rule is reachable and therefore useful.
- Assuming a prompt that was shown always receives a valid response.
## Claude Code: Permission Modes Quick Reference (2026)
Six named modes are the complete enumeration. All are set via `--permission-mode <mode>`, the `defaultMode` settings key, or `permissionMode` in subagent frontmatter. As of v2.1.200, `default` is labeled **Manual** in the CLI, `--help`, and the VS Code/JetBrains extensions, and `manual`/`"manual"` is accepted as an alias for `default` everywhere the mode is configured — treat `default` and `manual` as the same mode when writing tooling against it.
| Mode | What runs without prompting | Key notes |
|------|----------------------------|-----------|
| `default` (aka Manual, v2.1.200+) | Reads only | Starting mode; Shift+Tab cycles default → acceptEdits → plan |
| `acceptEdits` | Reads, file edits, common filesystem commands | Auto-approves edits in working directory only; never auto-approves [protected-path](#protected-paths) writes |
| `plan` | Reads only | Research without writing; exit via Shift+Tab or plan approval |
| `auto` | Everything except protected-path writes, with background classifier checks | v2.1.83+ required; protected-path writes route to the classifier, not auto-approval; see below |
| `dontAsk` | Only pre-approved tools (allow-list only) | Named headless auto-deny mode; for CI/locked environments; never appears in Shift+Tab cycle; protected-path writes are denied outright |
| `bypassPermissions` | Everything, no classifier, including protected-path writes | Isolated containers/VMs only; never appears in default cycle; explicit `ask` rules and `rm -rf /` / `rm -rf ~` still prompt as a circuit breaker |
**Shift+Tab cycle**: default → acceptEdits → plan. Optional modes slot in after plan: `bypassPermissions` first (if started with `--permission-mode bypassPermissions` or `--allow-dangerously-skip-permissions`), then `auto` last (if eligible). `dontAsk` never appears in the cycle.
### Protected paths
A small set of paths (`.git`, `.config/git`, `.vscode`, `.idea`, `.husky`, `.cargo`, `.devcontainer`, `.yarn`, `.mvn`, `.claude` except `.claude/worktrees`, plus files like `.gitconfig`, shell rc files, and package-manager rc files) are never auto-approved in any mode except `bypassPermissions`, and the check runs *before* `permissions.allow` rules are evaluated — an `Edit(.claude/**)` allow rule does not change this. This is the pattern to copy for any runtime: repository metadata and the agent's own config directory should have a hard-coded carveout that ordinary allow rules cannot reach, independent of mode.
**`auto` mode** — model-classifier approval system:
- A server-configured classifier model (independent of the session's `/model` selection) evaluates each action before execution; do not hard-pin a specific classifier model name in downstream tooling — it can change without a version bump to the mode's behavior.
- Two-stage: fast single-token filter, then chain-of-thought on flagged actions
- Classifier sees user messages, tool calls, and CLAUDE.md content; tool *results* are stripped and separately scanned for injected instructions
- Decision order: allow/deny rules resolve first (except protected-path writes, which always route to the classifier) → read-only/in-directory edits auto-approve → everything else hits the classifier
- On entering `auto`, broad allow rules (`Bash(*)`, `PowerShell(*)`, wildcarded interpreters, package-manager run commands, `Agent` allow rules) are dropped and restored on exit; narrow rules like `Bash(npm test)` carry over
- If the classifier blocks 3 consecutive times or 20 total times, auto mode pauses and prompting resumes; in non-interactive (`-p`) mode there is no user to prompt, so the run aborts instead
- Eligibility (plan tier, minimum model, provider) is gated per-provider and changes over time — verify current gating in `/en/permission-modes` at ship time rather than trusting a cached list
- **Trap:** `defaultMode: "auto"` set in `.claude/settings.json` or `.claude/settings.local.json` is silently ignored (v2.1.142+) so a repository cannot grant itself auto mode; it must be set in user (`~/.claude/settings.json`) or managed settings
- `permissionMode: auto` in subagent frontmatter is ignored when the parent is in auto mode — the parent's auto mode applies to all subagent actions, checked at three points: before spawn (task description), during execution (each action), and after completion (a full-history review that can prepend a security warning to the result, v2.1.178+)
- Plugin subagents cannot set `permissionMode`
- `disableAutoMode: "disable"` in managed settings removes auto from the Shift+Tab cycle and rejects `--permission-mode auto`
- Source: `code.claude.com/docs/en/permission-modes` and `anthropic.com/engineering/claude-code-auto-mode`
**`dontAsk` mode** — named headless auto-deny: every tool call that would normally prompt is auto-denied; only `allow`-rule-matched tools and read-only Bash commands execute, and an MCP tool marked `requiresUserInteraction` is denied even when an allow rule matches it (its consent card needs an answer this mode never collects). This is the correct mode for CI scripts that must not hang waiting for approval.
**Background subagents and the "auto-deny vs. escalate" judgment call**: prior to v2.1.186, a background subagent that hit a tool call requiring approval was auto-denied outright, because the main session had no way to interrupt it — subagents got stuck in silent denial loops. Since v2.1.186, the denied-by-default call instead surfaces as a prompt in the parent/main session, labeled with the subagent's name; the parent can approve or deny that single call without stopping the subagent. Treat this as the reference pattern for this skill's own "promptability" principle: **auto-deny is the correct answer only when no controller exists to escalate to.** When a background or worker context has any reachable parent/leader session, route the pending approval there as a first-class object (see [Permission Runtime Model](references/permission-runtime-model.md)) rather than defaulting to silent denial.
**Inheritance rules**: a parent session in `auto` overrides any `permissionMode` set in subagent frontmatter. Plugin-shipped agents are additionally restricted: `hooks`, `mcpServers`, and `permissionMode` fields in plugin agent definitions are silently ignored by the runtime.
### Rule syntax an expert checks first
- **Evaluation order is fixed, not precedence-weighted**: deny, then ask, then allow — first match wins regardless of specificity. A broad `Bash(aws *)` deny still blocks a narrower `Bash(aws s3 ls)` allow; there is no allowlist-exception mechanism inside a deny rule.
- **Parameter-matching rules** (`Tool(param:value)`, v2.1.178+) let deny/ask rules gate on any top-level scalar input field, e.g. `Agent(model:opus)` or `Agent(isolation:worktree)` or `Bash(run_in_background:true)`. This is distinct from — and composes with — the older `Agent(AgentName)` subagent-identity rule. Fields a tool already canonicalizes (`command` for Bash, `file_path` for Read/Edit/Write) are excluded from this path and must use the tool's own specifier syntax; a `Bash(command:rm *)` rule is silently ignored with a startup warning because it would be bypassable by a compound command.
- **Path-anchor bugs are the most common review finding**: `//path` is filesystem-absolute; `/path` is relative to *the settings file that defines the rule*, not the project root or the CLI's cwd. A `Read(/secrets/**)` deny written into `~/.claude/settings.json` blocks `~/.claude/secrets/**`, not a project's `secrets/` directory — reviewers should flag every single-leading-slash path rule in user settings as a likely mistake.
- **Symlinks split allow/deny asymmetrically**: allow rules require both the symlink path *and* its resolved target to match (otherwise it falls back to prompting); deny rules fire if *either* the symlink path or the target matches. A symlink into an allowed directory pointing outside it is not auto-approved by that fact alone.
- **Hooks are best-effort, not enforcement**: a `PreToolUse` hook's `if` field scopes it with permission-rule syntax (e.g. `if: "Bash(rm *)"`), but the filter fails open — if the Bash command can't be parsed, the hook runs anyway. Deny/ask permission rules are evaluated independently of what a hook returns, so a hook cannot be the sole enforcement point for a hard boundary; it can add checks, not replace deny/ask rules. See [`../agents-hooks/SKILL.md`](../agents-hooks/SKILL.md) for hook event and matcher design.
### Settings precedence, briefly (permission-audit nuance only)
Full precedence chain — **managed settings > CLI arguments > local project (`.claude/settings.local.json`) > shared project (`.claude/settings.json`) > user (`~/.claude/settings.json`)** — is [`../ai-coding-agents-settings-policy/SKILL.md`](../ai-coding-agents-settings-policy/SKILL.md)'s territory; use it for full source layering and managed-policy design.
The one nuance load-bearing for a *permission* audit: that precedence order governs plain settings (e.g. `spinnerTipsEnabled`), but permission rules do not simply follow "higher scope wins." **A deny rule from any scope blocks the action regardless of scope precedence** — a user-level deny blocks a project-level allow and a project-level deny blocks a user-level allow, because deny is evaluated before allow at every scope and rules merge across scopes rather than one file overriding another wholesale. Do not assume "project settings win over user settings" applies to allow-vs-deny conflicts — check which rule is a deny before applying the file-precedence mental model.
## OpenAI Codex: `AskForApproval` Policy Enum
Source: live `codex-rs/protocol/src/protocol.rs` (`main` branch, re-verified July 2026) — re-check before shipping, since Codex's config surface has changed shape twice within a few months.
TOML key: `approval_policy` (type `Option<AskForApproval>`, in the top-level `[config]` section of `~/.codex/config.toml`)
| Variant | TOML value | Behavior |
|---------|-----------|---------|
| `UnlessTrusted` | `"untrusted"` | Auto-approves only commands `is_safe_command()` judges "known safe" and read-only; asks for everything else |
| `OnRequest` | `"on-request"` (also accepts legacy `"on-failure"` as a deserialization alias) | **Default.** The model decides when to ask for approval |
| `Granular(GranularApprovalConfig)` | `"granular"` + sub-keys | Fine-grained per-category control: `sandbox_approval`, `rules` (execpolicy `prompt` rules), `skill_approval`, `request_permissions`, `mcp_elicitations` |
| `Never` | `"never"` | Never submits commands for approval; failures returned immediately to the model |
**Correction from an earlier snapshot of this skill:** a prior pinned-commit source (2026-05-22) showed `UnlessTrusted` serializing as `"unless-trusted"` and a separate deprecated `OnFailure` variant ("all commands auto-approved, rely on the sandbox"). The live source now serializes `UnlessTrusted` as `"untrusted"`, and `OnFailure` is gone as a distinct variant — `"on-failure"` is now only a backward-compatible TOML alias that deserializes into `OnRequest`, so a config still carrying `approval_policy = "on-failure"` gets `OnRequest` behavior, not the old always-auto-approve behavior. Treat this as a general lesson, not just a one-time fix: **pinned-commit citations for a fast-moving CLI's config surface expire faster than the rest of this skill; re-fetch the live source (or current config-reference docs) before trusting an enum-value table, and prefer `on-request`/`never` explicitly over relying on the `on-failure` alias.**
`GranularApprovalConfig` lets operators selectively enable or suppress approval prompts per action class, allowing a policy like "always ask for MCP tool calls, never ask for skill-script approval."
Design rule: treat `AskForApproval` as the canonical approval-mode type when implementing Codex-compatible runtimes — do not invent a parallel enum. Do not write new code that branches on a standalone `on-failure` semantic; it is an alias, not a policy.
## Cross-Platform Patterns (Goose)
Goose exposes two approval surfaces the current skill does not model: **identity-aware approvals via an OIDC proxy**, and **ACP-bridged approvals** where approvals must round-trip across a stdio boundary to a delegating agent.
### Identity-aware approvals (OIDC proxy)
Goose ships an `oidc-proxy/` crate that bridges agent calls to external providers through an OIDC-authenticated proxy. Approvals tied to external side-effects (deploy, merge, paid API use) can be gated on the authenticated identity, not just on "the user accepted a local prompt."
- **Pattern:** treat user identity as a permission-context field alongside mode and rule sources. Certain rule classes (destructive-on-prod, financial, third-party) can require fresh auth proof, not just a prior local allow.
- **Anti-pattern:** assuming the OS user running the CLI is the authorization principal for remote/side-effecting approvals. Shared dev machines, CI agents, and teammate-session handoff all break that assumption.
- **Recipe:** add an optional `auth_proof` field to the permission context; rules can declare `require_fresh_auth: true`. For those, every N minutes or per-call, the host demands re-authentication via OIDC proxy. Record the identity with the approval outcome for audit.
### ACP-bridged approvals
When a session delegates work to an external ACP agent (see `ai-coding-agents-remote-runtime` agent-delegating mode, and `ai-coding-agents-provider-runtime` agent-as-provider), tool approvals requested by the delegated agent must round-trip back to the orchestrator's local UI. The skill's current worker-routing model assumes same-process workers.
- **Pattern:** approvals raised by an ACP-delegated agent travel back through the ACP control channel, are materialized as a pending approval object in the orchestrator, rendered in the orchestrator's UI, resolved locally, and the outcome is sent back through ACP to the delegated agent. Stable request IDs are required end-to-end.
- **Anti-pattern:** auto-approving everything from a "trusted" delegated agent. ACP does not bound the delegated agent's tool use; without round-trip approval, the orchestrator loses audit and policy.
- **Recipe:** add a third routing leg in the permission-routing reference: **orchestrator ↔ ACP-delegated agent**. The delegated agent's approval requests are first-class runtime objects in the orchestrator's permission context, with stable IDs, cancellation support, and cross-process cancellation on session death.
## Navigation
### References
- [`references/permission-runtime-model.md`](references/permission-runtime-model.md) — Central permission context, plan-mode restore, and host-owned rule layers
- [`references/permission-routing-local-remote-and-worker.md`](references/permission-routing-local-remote-and-worker.md) — Approval flows for REPL, remote sessions, and swarm workers
- [`references/openai-codex-request-permissions-and-split-policy.md`](references/openai-codex-request-permissions-and-split-policy.md) — OpenAI Codex split filesystem policy, protected metadata, `request_permissions`, and hostile-case tests
- [`references/openai-codex-execpolicy-and-network-proxy.md`](references/openai-codex-execpolicy-and-network-proxy.md) — OpenAI Codex executable policy engine and network proxy boundary patterns
- [`references/hostile-path-test-checklist.md`](references/hostile-path-test-checklist.md) — Adversarial path, symlink, and containment cases for permission-boundary tests
### Data
- [`data/sources.json`](data/sources.json) — Primary documentation and source references for approval-system design
### Related Skills
- [`../agents-hooks/SKILL.md`](../agents-hooks/SKILL.md)
- [`../agents-swarm-orchestration/SKILL.md`](../agents-swarm-orchestration/SKILL.md)
- [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md)
## Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- The patterns here are grounded in a local April 2026 `claude_code` source snapshot. Verify current permission field names, event names, and remote control schemas before shipping.
- Approval UX and sandbox semantics are runtime-specific. Preserve the architecture patterns, but re-check exact prompt behavior for the target client.
## Learnings Loop
Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.