Managed permission profiles with non-cwd writable roots incorrectly tell the model that cwd is writable
What version of Codex CLI is running?
codex-cli 0.146.1, Homebrew installation
What subscription do you have?
Pro (Lite), 5×
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Darwin 25.6.0 arm64 arm
What terminal emulator and version are you using?
tmux 3.6a
Codex doctor report
Not collected. This is a deterministic prompt-generation issue reproduced from the effective permission profile and verified against the rust-v0.146.1 source.
What issue are you seeing?
A managed permission profile that extends :read-only and grants write access exclusively to directories outside the current working directory generates contradictory model instructions.
For example, with a profile granting write access to /Users/me/shared, while cwd is /Users/me/project, Codex tells the model:
sandbox_modeisworkspace-write: The sandbox permits reading files, and editing files incwdandwritable_roots.
It subsequently lists /Users/me/shared as a writable root and omits /Users/me/project.
The effective policy keeps /Users/me/project read-only. Consequently, an apply_patch targeting that directory correctly requests approval.
The approval behavior is correct. The generated instruction falsely grants cwd write access, encouraging the model to attempt writes that predictably trigger approval.
Relevant code
Release commit: 79b4f03d (rust-v0.146.1)
The primary classification occurs in sandbox_prompt_from_policy(), lines 193–206:
let writable_roots = file_system_policy.get_writable_roots_with_cwd(cwd);
if writable_roots.is_empty() {
(SandboxMode::ReadOnly, None)
} else {
(SandboxMode::WorkspaceWrite, Some(writable_roots))
}
Any restricted profile with at least one writable root is classified as WorkspaceWrite. This does not check whether any writable root contains cwd.
The selected workspace_write.md template, line 1 unconditionally says:
The sandbox permits reading files, and editing files in `cwd` and `writable_roots`.
The existing builds_permissions_from_profile test, lines 187–218 uses /tmp/repo as its writable root while cwd is /tmp. It expects the workspace-write label but does not assert whether the generated statement about cwd is accurate.
Runtime enforcement separately checks each patch target using can_write_path_with_cwd() in safety.rs, lines 135–203:
file_system_sandbox_policy.can_write_path_with_cwd(&abs, &native_cwd)
An out-of-root target becomes SafetyCheck::AskUser, which is converted into ExecApprovalRequirement::NeedsApproval in apply_patch.rs, lines 34–69.
What steps can reproduce the bug?
Configure a named profile whose writable root does not contain the working directory:
default_permissions = "manual"
approval_policy = "on-request"
[permissions.manual]
extends = ":read-only"
[permissions.manual.filesystem]
"/Users/me/shared" = "write"
- Start Codex with
/Users/me/projectascwd. - Ask Codex to report its injected filesystem permission instructions.
- Observe that it reports
sandbox_mode = workspace-writeand claimscwdis editable. - Observe that the listed writable roots contain
/Users/me/sharedand omit/Users/me/project. - Ask Codex to create a file in
/Users/me/projectusingapply_patch. - Observe the approval request.
What is the expected behavior?
Generated permission instructions should accurately describe the effective policy.
For arbitrary managed profiles, suitable wording would be:
The sandbox permits writes within the writable roots listed below. The current working directory is writable only when covered by one of those roots.
Alternatively, prompt classification could distinguish a traditional workspace-write profile from a restricted managed profile having unrelated writable roots.
The existing enforcement and approval behavior should remain unchanged.
Additional information
I searched open and closed issues and pull requests using the exact generated wording, sandbox_prompt_from_policy, workspace-write, writable roots, managed permission profiles, and extends = ":read-only". I found related permission-profile reports but no exact duplicate.
The closest reports concern read-scope expectations (#23911), ignored custom profiles (#28281), actual temporary-directory write enforcement (#32395), and an alleged out-of-root apply_patch bypass (#31434).
1 Comment
Verified on current
main(1f41cc5d92): unchanged.sandbox_prompt_from_policystill classifies any restricted profile with ≥1 writable root asWorkspaceWritewithout checking whether any root containscwd(https://github.com/openai/codex/blob/1f41cc5d92/codex-rs/prompts/src/permissions_instructions.rs#L193-L206), and theworkspace_write.mdtemplate still unconditionally asserts editing is permitted "incwdandwritable_roots". Your analysis of the enforcement side is also right —can_write_path_with_cwdkeeps the actual policy correct, so this is purely a prompt/reality divergence that manufactures predictable approval churn (the model is told cwd is writable, tries, and gets bounced every time).Fix is small and local: compute
cwd_writable = writable_roots.iter().any(|root| root.contains(cwd))in the same function and select between two template variants — the current text when true, and "editing files only inwritable_roots;cwdis read-only" when false (plus listing cwd's status explicitly). The existingbuilds_permissions_from_profiletest is the right place for the regression assertion, since as you note it already constructs exactly this shape (/tmp/reporoot,/tmpcwd) and just doesn't check the sentence.