Codex Desktop Ask for approval silently disables shell escalation in projectless tasks

Open 💬 1 comment Opened Jul 12, 2026 by 0xdevalias

What version of the Codex App are you using (From “About Codex” dialog)?

Codex Desktop 26.707.51957 (bundle 5175), bundled codex-cli 0.144.0-alpha.4.

What subscription do you have?

Pro

What platform is your computer?

macOS / Darwin arm64

What issue are you seeing?

In a newly created projectless Codex Desktop task, selecting Ask for approval silently replaces the configured approval_policy = "on-request" with a granular policy that has sandbox_approval = false:

GranularApprovalConfig {
    sandbox_approval: false,
    rules: false,
    skill_approval: false,
    request_permissions: true,
    mcp_elicitations: true
}

This is a regression from the previous interactive approval behavior: the agent can still see and attempt the normal exec_command escalation interface, but sandbox_permissions = "require_escalated" is rejected before an approval prompt can be created. This has effectively broken existing chats/workflows that previously let Codex ask for approval and retry a blocked tool call.

The alternative request_permissions mechanism is not an adequate transparent replacement in the current product contract:

  • the UI still calls the mode Ask for approval and says it will “Always ask to edit external files and use the internet”;
  • the effective policy and the reason ordinary shell escalation is disabled are not shown;
  • the agent still has an exec_command schema containing sandbox_permissions = "require_escalated";
  • existing prompts, workflows, and user requests naturally direct the agent to use that interface;
  • the user's explicit approval_policy = "on-request" is overridden;
  • neither the user nor apparently the agent is told how all previously approval-gated command cases should be translated into a separate pre-grant request.

This creates a user-visible contradiction: Approve for me supports the ordinary escalation path, while Ask for approval rejects it before the user can be asked.

Deterministic A/B reproduction

The same task and same harmless command were used for both modes:

curl --max-time 2 --silent --show-error http://127.0.0.1:9229/json/list

The tool call included:

{
  "sandbox_permissions": "require_escalated",
  "justification": "May I run the same read-only localhost probe to verify the effective approval behavior?"
}

With Ask for approval, Desktop injected the granular policy above and rejected the call before prompting:

approval policy is Granular(GranularApprovalConfig { sandbox_approval: false, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: true }); reject command — you cannot ask for escalated permissions if the approval policy is Granular(GranularApprovalConfig { sandbox_approval: false, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: true })

With Approve for me, the effective state changed to approval_policy = "on-request" with an automatic reviewer. The identical escalated command executed successfully and returned the local Electron Node inspector entry.

Switching back to Ask for approval restored the granular policy and reproduced the exact pre-prompt rejection.

Local configuration

Relevant ~/.codex/config.toml:

approval_policy = "on-request"
approvals_reviewer = "user"
sandbox_mode = "workspace-write"

[sandbox_workspace_write]
writable_roots = [
  "~/.npm",
  "~/.cache/clang/ModuleCache",
]

The Desktop task host overrides this approval policy when Ask for approval is selected.

Desktop bundle source trace

The installed archive was extracted read-only for inspection:

/Applications/Codex.app/Contents/Resources/app.asar
SHA-256: 26708d5be316b43786ba00ea8581317426e44ff508e0d5cce40f53181582e027
Size: 195,064,103 bytes

Because the production renderer chunks are minified to one physical line, line numbers are not useful. The table below gives the exact archive member, complete SHA-256, file size, and zero-based byte offset for every cited symbol/string.

| Evidence | Exact archive member under app.asar | SHA-256 | Size | Byte offset |
| --- | --- | --- | ---: | ---: |
| Projectless tasks select granular: function Z1t(...) | webview/assets/app-initial~app-main~new-thread-panel-page~appgen-library-page~hotkey-window-thread-page~ho~iufn7mg3-k1satKyX.js | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 2,685,902 |
| Permission override construction: function a1t(...) | same ...k1satKyX.js member | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 2,676,285 |
| Turn-start permission resolution: function svt(...) | same ...k1satKyX.js member | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 2,204,506 |
| “Ask for approval” click handler: let Ge=()=>{...} | same ...k1satKyX.js member | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 8,287,625 |
| “Ask for approval” displayed label | same ...k1satKyX.js member | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 8,285,540 and 8,291,885 |
| “Always ask to edit external files and use the internet” description | same ...k1satKyX.js member | 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac | 8,856,468 | 8,291,630 |
| Agent-mode-to-policy mapping: function nS(...) | webview/assets/app-initial~app-main~projects-index-page~hotkey-window-thread-page~local-environments-setti~jjjs6eag-De99_ILj.js | b557f54f16bbb4d4e2176d63ded3fd310ca38d91f9d32124a55d96a5e477e6c1 | 443,446 | 347,743 |
| Hard-coded granular policy: hS={granular:...} | same ...De99_ILj.js member | b557f54f16bbb4d4e2176d63ded3fd310ca38d91f9d32124a55d96a5e477e6c1 | 443,446 | 349,527 |

The offsets can be reproduced after extracting the archive with commands such as:

rg --byte-offset -o 'function Z1t\(' '<extracted-asar>/webview/assets/app-initial~app-main~new-thread-panel-page~appgen-library-page~hotkey-window-thread-page~ho~iufn7mg3-k1satKyX.js'
rg --byte-offset -o 'hS=\{granular:' '<extracted-asar>/webview/assets/app-initial~app-main~projects-index-page~hotkey-window-thread-page~local-environments-setti~jjjs6eag-De99_ILj.js'

The renderer source shows that projectless tasks deliberately default to granular rather than auto:

function Z1t({isProjectless:e,requirements:t}) {
  return e && sr(`granular`,t) ? `granular` : `auto`
}

The Ask for approval click handler selects that computed default (se):

let Ge = () => {
  Ve(se)
  Se(null)
  W(se)
  L(!1)
}

The renderer hard-codes the granular policy:

hS = {
  granular: {
    sandbox_approval: false,
    rules: false,
    skill_approval: false,
    request_permissions: true,
    mcp_elicitations: true
  }
}

and maps that mode to a workspace sandbox with the explicit granular override:

case `granular`:
  return Qx(workspaceRoots, void 0, hS)

Relevant extracted chunks:

webview/assets/app-initial~app-main~projects-index-page~hotkey-window-thread-page~local-environments-setti~jjjs6eag-De99_ILj.js
SHA-256: b557f54f16bbb4d4e2176d63ded3fd310ca38d91f9d32124a55d96a5e477e6c1

webview/assets/app-initial~app-main~new-thread-panel-page~appgen-library-page~hotkey-window-thread-page~ho~iufn7mg3-k1satKyX.js
SHA-256: 4bd5e9614579174f1d612786cc693a3f1babd3dd1884d8949c6fea59091e19ac

Runtime source trace

The upstream references below are pinned to repository commit 9e552e9d15ba52bed7077d5357f3e18e330f8f38, rather than floating main links:

| Evidence | Repository file | Git blob SHA-1 | Size | Lines |
| --- | --- | --- | ---: | ---: |
| Explicit shell escalation rejection outside OnRequest | codex-rs/core/src/tools/handlers/shell.rs | 87a9301093453a5c3ab4545cc3f62e0a861e7c98 | 8,738 | 122–138 |
| Granular prompt/category and request_permissions routing | codex-rs/prompts/src/permissions_instructions.rs | 1c19d008cf33833e1c7d509e6f32e61ddb8a6499 | 15,555 | 332–400 |

Current Codex runtime source explicitly rejects a fresh shell sandbox override under every policy other than OnRequest:

https://github.com/openai/codex/blob/9e552e9d15ba52bed7077d5357f3e18e330f8f38/codex-rs/core/src/tools/handlers/shell.rs#L122-L138

if effective_additional_permissions
    .sandbox_permissions
    .requests_sandbox_override()
    && !effective_additional_permissions.permissions_preapproved
    && !matches!(
        turn.approval_policy.value(),
        codex_protocol::protocol::AskForApproval::OnRequest
    )
{
    // reject before creating an approval
}

The permission-instruction generator confirms that granular mode exposes direct shell permission requests only when sandbox_approval is enabled, while this Desktop policy instead enables request_permissions:

https://github.com/openai/codex/blob/9e552e9d15ba52bed7077d5357f3e18e330f8f38/codex-rs/prompts/src/permissions_instructions.rs#L332-L400

let shell_permission_requests_available =
    exec_permission_approvals_enabled && sandbox_approval_prompts_allowed;
let request_permissions_tool_prompts_allowed =
    request_permissions_tool_enabled && granular_config.allows_request_permissions();

So the runtime rejection is internally consistent with the policy Desktop selected. The regression/product bug is the Desktop mapping and resulting contract: a UI mode named Ask for approval silently stops supporting the established shell approval flow, overrides explicit on-request configuration, and leaves both users and agents facing an exposed escalation interface that cannot work.

What steps can reproduce the bug?

  1. Set the relevant config values:

``toml
approval_policy = "on-request"
approvals_reviewer = "user"
sandbox_mode = "workspace-write"
``

  1. Create a fresh projectless task in Codex Desktop.
  2. Select Ask for approval in the composer permission menu.
  3. Inspect the effective developer permission instructions or rollout context. Observe GranularApprovalConfig with sandbox_approval: false.
  4. Ask Codex to perform a harmless command using sandbox_permissions = "require_escalated".
  5. Observe that the runtime rejects it before displaying an approval prompt.
  6. Switch the same task to Approve for me.
  7. Retry the identical call. Observe that the effective policy becomes on-request with automatic review and the escalation path works.
  8. Switch back to Ask for approval and observe the granular pre-prompt rejection again.

What is the expected behavior?

Ask for approval should preserve the established interactive approval behavior and the user's configured approval_policy = "on-request", including allowing Codex to request approval for a blocked command and retry it.

If projectless tasks must use granular permissions instead, the product needs a complete and unambiguous contract:

  1. clearly distinguish granular permission grants from ordinary command approval in the UI;
  2. do not expose or recommend sandbox_permissions = "require_escalated" when it is guaranteed to fail;
  3. ensure the agent reliably translates every relevant blocked filesystem/network operation into request_permissions before retrying;
  4. explain why explicit approval_policy = "on-request" configuration is being overridden;
  5. provide a migration path for existing chats and workflows that relied on interactive shell escalation.

Regression impact

  • Existing chats that previously escalated blocked commands now fail before prompting.
  • The agent may repeatedly choose an escalation interface that the host has made unusable.
  • Users cannot tell from the Ask for approval label that the underlying approval model has changed.
  • Per-command approval is important because it lets the user inspect the exact requested action; silently replacing it with a different capability-grant workflow changes the security and UX contract.
  • The behavior makes projectless Desktop chats feel broken even though the UI still advertises approval-based operation.

Related issues

  • #23359 — same lower-level granular/tool-schema mismatch, but does not cover Desktop's projectless-only UI mapping, explicit config override, or this A/B mode reproduction.
  • #31270 — related problems with permission-profile escalation, but concerns restrictions remaining after approval rather than approval being impossible to request.
  • #27575 — related Desktop approval-policy inheritance drift in automations.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗