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:
- Which permission and sandbox fields enter at turn start?
- Where do those fields land inside the runtime?
- Where do pre/post hooks and permission-request hooks attach?
- How does
ToolOrchestratorchoose skip, reject, or approval? - How are the first sandbox attempt and retry attempt selected?
- 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
- Pinned openai/codex source snapshot
- TurnStartParams approval_policy, sandbox_policy, and permissions
- TurnContext approval_policy, permission_profile, and sandbox policy helpers
- SessionConfiguration apply_updates for approval, permission profile, and sandbox policy
- AskForApproval
- GranularApprovalConfig
- SandboxPolicy
- ToolRegistry pre-tool-use hooks
- ToolRegistry post-tool-use hooks
- shell handler enters ToolOrchestrator
- apply_patch handler enters ToolOrchestrator
- ApprovalCtx, PermissionRequestPayload, and ExecApprovalRequirement
- default_exec_approval_requirement
- sandbox_override_for_first_attempt and denied-read protection
- Approvable, Sandboxable, and ToolRuntime
- SandboxAttempt
- ToolOrchestrator module header
- ToolOrchestrator approval phase
- ToolOrchestrator first sandbox attempt
- ToolOrchestrator sandbox-denied retry
- request_approval and PermissionRequest hooks
- ShellRuntime approval and permission request payload
- ShellRuntime executes with SandboxAttempt
- ApplyPatchRuntime approval and permission request payload
- ApplyPatchRuntime executes with SandboxAttempt
- ExecApprovalRequestEvent
- ApplyPatchApprovalRequestEvent
- ReviewDecision