Start with a normal action: the model wants to run tests. The shell tool call looks valid. Before anything runs, Codex still needs to know the current working directory, writable roots, network policy, approval mode, and whether any hook wants to block or rewrite the request.

That is the permission path this article follows. Permission is not a single UI switch and not a prompt-only promise. It is a runtime path: the turn carries policy, registry runs hooks, the orchestrator decides approval and sandboxing, and the concrete runtime executes only inside a selected SandboxAttempt.

In this article, side effects mean actions that touch the outside environment: starting processes, writing files, widening filesystem access, using the network, or retrying a denied sandboxed attempt with broader authority.

Evidence boundary. This article describes the policy fields, hook calls, approval events, sandbox selection, and retry logic visible in the public openai/codex source. It uses gate and authority as reading aids for source objects such as AskForApproval, PermissionProfile, ExecApprovalRequirement, ToolOrchestrator, and SandboxAttempt. It does not infer private guardian model behavior or undisclosed operating-system sandbox details.

This part answers six questions:

  1. Which permission and sandbox fields enter at turn start?
  2. Where do those fields land inside the runtime?
  3. Where do pre/post hooks and permission-request hooks attach?
  4. How does ToolOrchestrator choose skip, reject, or approval?
  5. How are the first sandbox attempt and retry attempt selected?
  6. How do we separate a model request from runtime authority?

1. Permission Enters With the Turn

The boundary appears before any shell handler runs. App-server v2 TurnStartParams includes approval_policy, sandbox_policy, and permissions. The permissions field selects a named permission profile and cannot be combined with sandboxPolicy.

In core, TurnContext stores approval_policy, permission_profile, the managed network proxy, and platform sandbox settings. Its helpers file_system_sandbox_policy() and network_sandbox_policy() project the active PermissionProfile into runtime policies.

Turn Field Runtime Object Meaning
approval_policy AskForApproval When Codex should request approval or return failure.
sandbox_policy SandboxPolicy Legacy sandbox shape: full access, read-only, workspace-write, and network flags.
permissions PermissionProfile A richer profile projected into filesystem and network policies.
runtime_workspace_roots workspace roots The roots against which writable workspace policy is interpreted.

Session updates preserve this shape. apply_updates merges approval policy first, then either a permission-profile projection or a legacy sandbox-policy projection into the session configuration.

2. Separate the Terms Before Reading the Gates

Approval, permissions, sandboxing, and hooks all describe authority, but they answer different questions in the source.

Term Source Handle Question Answered
Approval policy AskForApproval Should this action ask a user, guardian, or hook for a decision?
Permission profile PermissionProfile Which filesystem, network, and additional permissions are active?
Exec requirement ExecApprovalRequirement Is this request skipped, approved interactively, or forbidden?
Sandbox attempt SandboxAttempt Which concrete execution environment is used for this attempt?
Hooks pre/post/permission hooks Where extension or policy code can intercept the flow.

AskForApproval defines modes such as OnRequest, Granular, and Never. SandboxPolicy describes execution restrictions such as danger-full-access, read-only, external-sandbox, and workspace-write. Approval decides whether to ask. Sandbox policy decides the shape of containment.

3. Registry Hooks Run Before the Handler

Before the handler executes, ToolRegistry gives pre-tool-use hooks a chance to respond. The dispatch path obtains a pre-tool-use payload, runs run_pre_tool_use_hooks, and either blocks the call, rewrites the invocation input, or continues.

After successful handler execution, post-tool-use hooks can add context or replace model-visible output. That logic is visible in the post hook block. These hooks wrap the handler. They are distinct from permission-request hooks, which sit inside approval.

Pre/post hooks intercept tool execution. Permission-request hooks intercept an approval request before it reaches guardian or user review.

4. Side-Effecting Tools Enter the Orchestrator

The shell handler builds a ShellRequest, then creates ToolOrchestrator and ShellRuntime. The apply-patch path does the same after constructing an ApplyPatchRequest: it delegates to the same orchestrator.

The module header of orchestrator.rs states the sequence: approval, sandbox selection, attempt, and retry with escalation on denial. The generic shape comes from Approvable, Sandboxable, and ToolRuntime.

Keep following one request: the model wants to run npm test in the current workspace. Once it enters the orchestrator, the record to track is closer to this shape:

{
  "tool": "shell",
  "input": {"cmd": "npm test", "cwd": "workspace"},
  "approval_policy": "on-request",
  "sandbox_attempt": {
    "type": "workspace-write",
    "permission_profile": "project-write",
    "workspace_roots": ["workspace"]
  },
  "retry_reason": "sandbox_denied"
}

This separates the boundaries. Approval policy decides whether a review is needed. The sandbox attempt selects the first permission profile. The tool runtime turns that attempt into an executable environment. Only when failure looks like sandbox denial does the orchestrator consider escalation or retry.

5. First Gate: Does This Need Approval?

In ToolOrchestrator::run, approval comes first. Codex reads the active filesystem and network sandbox policies, asks the tool for a custom exec_approval_requirement(req), and otherwise falls back to default_exec_approval_requirement.

The default rule is compact: Never and OnFailure do not ask by default; OnRequest and Granular ask when filesystem access is restricted; UnlessTrusted always asks. Granular policy can convert a disabled category into Forbidden, so the runtime rejects instead of presenting a prompt.

approval skeleton:
  tool.exec_approval_requirement(req)
      or default_exec_approval_requirement(policy, fs_policy)
        ↓
  Skip / Forbidden / NeedsApproval
        ↓
  continue / reject / request_approval(...)

6. Permission Hooks Can Answer Before User Review

The approval path has another interception point. request_approval runs permission-request hooks first when the tool provides a permission_request_payload. A hook may return allow, deny with a message, or no decision. Only the no-decision case falls through to guardian or user approval.

Shell provides a bash permission payload in ShellRuntime. Apply patch provides an apply_patch permission payload in ApplyPatchRuntime.

7. Second Gate: Select the First Sandbox Attempt

Once approval is satisfied, the orchestrator selects the first sandbox. This block computes any first-attempt override, asks the sandbox manager for the initial sandbox, and assembles a SandboxAttempt with sandbox type, permission profile, managed-network flag, cwd, workspace roots, and platform settings.

SandboxAttempt exposes env_for, which transforms a command plus active permission profile into an executable request. Shell uses it in ShellRuntime::run. Apply patch builds a filesystem sandbox context from the same attempt and returns SandboxErr::Denied when a failure looks like sandbox denial.

Sandbox is not a cleanup step after failure. It is the environment for each attempt. The runtime receives SandboxAttempt and executes inside that attempt.

8. Retry After Denial Runs Through Policy Again

If the first attempt succeeds, the orchestrator returns output. The interesting path starts with sandbox denial. The retry branch checks network-denial context, whether the tool may escalate on failure, whether unsandboxed execution can preserve the active filesystem policy, and whether approval policy allows a retry prompt.

If retry is allowed, Codex may request approval again with a :retry permission-request run id. After approval, it selects either SandboxType::None or another sandboxed retry attempt, depending on whether unsandboxed execution is allowed.

after sandbox denial:
  denial output + network policy
      ↓
  tool escalation rules
      ↓
  no-sandbox / network approval policy
      ↓
  maybe request_approval(call_id:retry)
      ↓
  retry attempt or denied output to the model

9. Approval Requests Are Structured Events

Approval prompts are protocol objects, not plain strings. ExecApprovalRequestEvent includes call id, approval id, turn id, command, cwd, reason, network context, proposed execpolicy amendment, additional permissions, and available decisions. ApplyPatchApprovalRequestEvent carries patch changes, reason, and grant root.

Shell calls session.request_command_approval in start_approval_async. Apply patch calls session.request_patch_approval in start_approval_async. The result is a ReviewDecision.

10. Rules for Reading This Path

Observation Source Question Handle
The model asks to run a command. Did the handler translate it into a side-effecting runtime request? shell/apply_patch handler to ToolOrchestrator.
A tool asks for approval. Is approval coming from default policy or a tool-specific requirement? exec_approval_requirement and default_exec_approval_requirement.
No user prompt appears. Did a permission-request hook already allow or deny it? run_permission_request_hooks.
A command runs in sandbox. Which sandbox, permissions, cwd, and network flags are on this attempt? SandboxAttempt.
A denied sandboxed command retries. Is it a network approval, no-sandbox retry, or terminal denial? ToolOrchestrator::run retry branch.

The permission path now closes: turn start carries approval and permission policy; TurnContext stores the runtime projection; registry hooks wrap tool entry and exit; side-effecting handlers delegate to ToolOrchestrator; the orchestrator decides approval, sandbox attempt, and denial retry.

The key distinction is that the model proposes an action. Runtime gates decide whether that action may touch the system. The next part can move outward again: how these structured events and history entries are projected by the TUI, app-server, and recovery paths into the same user visible facts.

Sources