Harness Notes

Permissions

Updated 2026-10-21

On this page

How to stop an agent doing something, and what each harness gives you to control it. Permission rules decide what the agent may do; a sandbox limits what a command can reach even when it is allowed.

Block a command, for example git push

Codex CLIClaude CodeCursor AgentOpenCodepi
How

A command rule file

A deny rule in the permission settings

A deny rule in the permission settings

A deny rule in the permission settings

An extension that blocks the tool call

Only with an extension

File.codex/rules/deny-push.rules.claude/settings.json.cursor/cli.jsonopencode.jsonc.pi/extensions/block-git-push.ts
Example
prefix_rule(
    pattern = ["git", "push"],
    decision = "forbidden",
    justification = "Pushing is a human decision",
    match = ["git push", ["git", "push", "origin", "main"]],
)
{
  "permissions": {
    "deny": [
      "Bash(git push:*)"
    ]
  }
}
{
  "permissions": {
    "deny": [
      "Shell(git:push*)"
    ]
  }
}
{
  "$schema": "https://opencode.ai/config.json",
  "permissions": [
    {
      "action": "shell",
      "resource": "*",
      "effect": "ask"
    },
    {
      "action": "shell",
      "resource": "git push *",
      "effect": "deny"
    }
  ]
}
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  pi.on("tool_call", async (event) => {
    if (isToolCallEventType("bash", event) && /(^|[;&|]\s*)git\s+push\b/.test(event.input.command)) {
      return { block: true, reason: "git push is blocked by policy" };
    }
  });
}
How it matches

ordered argv-token prefix matching; .rules files are auto-discovered below the rules directory of each configuration layer

shell-operator-aware Bash command matching with wrapper stripping and leading environment-assignment handling

Shell(commandBase) with an optional command:args glob; exact argument-string behavior is not fully documented

shell resources ending in space-star also match the command without arguments

extension-defined string matching over bash tool input

What wins

forbidden is stricter than prompt and allow and cannot be approved interactively

deny, then ask, then allow; a matching deny survives every permission mode and cannot be overridden by a hook allow

deny takes precedence over allow and survives --force/--yolo

last configured match wins; configured deny is evaluated before saved approvals and hooks

no native policy precedence; extension ordering and later input mutation matter

Watch out for

Command rules belong in .codex/rules/*.rules, not config.toml.

--ignore-rules skips user and project rule files; use a managed layer or OS containment when the invoking user must not be able to bypass the rule.

Wrappers and nested interpreters require separate coverage unless Codex's nested-command parser recognizes the invocation.

Environment runners and nested interpreters such as sh -c can hide the inner command; deny those runners too or use OS containment for a hard boundary.

Matching details are only partly documented.

Shell(git) is the unambiguous declarative form but blocks every git command.

For exact git-push matching, use a beforeShellExecution hook that returns permission deny or exits 2; other non-zero hook exits fail open.

Shell directory inference is best effort and shell runs with host authority.

A later configured broad allow can override an earlier configured deny; saved approvals cannot.

Stock pi has no declarative deny rule or built-in sandbox.

The extension owns shell parsing and must explicitly cover wrappers, interpreters, aliases, and scripts.

Project extensions load only after project trust.

What you can control

Codex CLIClaude CodeCursor AgentOpenCodepi
Decisions

allow, ask, deny

allow, ask, deny

allow, ask, deny

allow, ask, deny

None built in

Permission rules

Built in

Cover: sandbox escalation, command rules, MCP elicitation, permission request, skill approval

Match by: approval_policy plus command rules

Docs

Built in

Cover: tool, command, path, network, MCP

Match by: tool rules with tool-specific specifiers

Conflicts: deny, then ask, then allow; first match within the winning class

Docs

Built in

Cover: shell, read, write, web fetch, MCP

Match by: permission tokens with patterns

Docs

Built in

Cover: tool, command, path, external directory, skill, subagent, MCP

Match by: whole-value wildcards over action and resource

Conflicts: last configured match wins; agent rules append after global rules; configured deny survives saved approvals and permission hooks

Docs

Not supported

Stock pi has no built-in tool permission model; extensions run with the user's authority.

Docs
Sandbox

Built in

Limits: filesystem, network

Modes: read-only, workspace-write, danger-full-access

Enforced separately from permission rules.

Docs

Built in

Limits: filesystem, network

Enforced separately from permission rules.

2.1.304 fixes Linux sandbox startup for denyRead globs with literal bracket segments, and extends the deletion safety prompt to rm -r of mounted-drive roots. Publisher notes; no sandbox probe.

Docs

Not established

Not established

external_directory is permission policy, not proof of OS containment.

Not supported

No built-in execution sandbox is claimed.

Approval prompts

Built in

Modes: untrusted, on-request, never

approval_policy and sandbox_mode are separate inputs

0.163.0 release notes: saved-thread resume restores permissions and network approvals while respecting explicit overrides and current managed policy. The proxy checks permission before DNS lookup, rejects duplicate Host headers, and prevents credential aliases from bypassing inspection hooks.

Docs

Built in

Modes: default, acceptEdits, plan, auto, dontAsk, bypassPermissions

Remember an approval: once, session, project-command

2.1.302 published changes: claude -p and Agent SDK sessions with no permission mode start in default mode on third-party providers, with telemetry off or on a fresh install. Explicit --permission-mode auto retains auto. This is a launch default, not a change to configured-rule precedence.

Docs

Not established

Built in

Remember an approval: once, project

Allow always stores durable project-scoped proposed patterns; reject declines all pending requests in the session.

2.1 adds client.permission.saved.update({ id, action?, resource? }) / PATCH /api/permission/saved/:id for changing a saved grant’s tool action or resource pattern. action names the permission subject, not allow/ask/deny; this does not add an editable decision effect. Duplicate project/action/resource combinations return conflict.

Docs

Not supported

An extension must implement its own approval UI and policy.

Skills granting tools

Not supported

Codex does not interpret SKILL.md allowed-tools as a skill-scoped permission grant.

Docs

Built in

Field: allowed-tools

turn-scoped pre-approval while the skill is active; not a tool-availability restriction

2.1.304: under a policy allowing hooks from plugins only, a main-conversation skill whose hooks are excluded no longer receives its allowed-tools grant.

Docs

Not established

Not supported

The skill permission controls loading; skill frontmatter does not grant tool access.

Docs

Not supported

Hooks that decide

Built in

PreToolUse, PermissionRequest

Can: allow, deny

PreToolUse can only deny; PermissionRequest can allow or deny, and any deny wins.

Hook-definition trust and tool approval are separate controls.

Docs

Built in

PreToolUse, PermissionRequest, PermissionDenied

Can: allow, ask, deny

A hook allow does not override matching ask or deny rules; a blocking hook takes precedence over allow rules.

In 2.1.301, prompts for an ask decision from a plugin or skill hook identify its origin. Published telemetry adds classifier_verdict and permission_mode to tool_decision, and classifier_category when OTEL_LOG_TOOL_DETAILS=1.

2.1.302 published changes fix missing classifier_category on most auto-mode classifier blocks and preserve tool_use_id, tool_source and tool_parameters when the OpenTelemetry attribute limit is low; telemetry delivery remains unprobed.

2.1.304 mod tool.check can return decider: "person": only the permission dialog or host callback may approve; when neither can ask, the request is refused with the mod’s reason. The event also reports e.isReadOnly. This is the mod surface, separate from declarative PreToolUse hooks.

Docs

Built in

preToolUse, beforeShellExecution, beforeMCPExecution, beforeReadFile

Can: allow, deny, ask

Hook permission decisions are a separate surface from CLI permission files.

ask is not enforced for preToolUse.

Docs

Built in

permission.evaluate

Can: allow, ask, deny

The hook runs after configured-deny evaluation, so it cannot override a configured deny.

Since 2.0.30, MCP tool calls include their input in permission-evaluation metadata.input; absent input becomes {}. This does not change configured-deny precedence.

Docs

Only with an extension

tool_call, tool_result

An extension can implement a permission gate, but it must emit its own evidence.

Docs
Where rules live
  • ~/.codex/config.toml, user
  • .codex/config.toml, project
  • CLI flags and -c overrides, session

Priority between these files is not recorded here.

  • managed settings, managed
  • CLI flags, session
  • .claude/settings.local.json, project-local
  • .claude/settings.json, project
  • ~/.claude/settings.json, user

Listed highest priority first.

  • <project>/.cursor/cli.json, project
  • ~/.cursor/cli-config.json, user

Project settings override global ones.

  • ~/.config/opencode/opencode.json(c), user
  • opencode.json(c) and .opencode/opencode.json(c), project
  • agents.<id>.permissions, agent
No permission settings

Sources

Codex CLI
Claude Code
Cursor Agent
OpenCode
pi