Goal continuation can resume with a different sandbox/approval profile after restart

Open 💬 5 comments Opened Jun 15, 2026 by itsonlyfrans

Summary

When Codex Desktop restarts, updates, or resumes in the middle of an active long-running goal, the resumed turn can receive a different environment_context / permission profile than the previous turn. This can cause the agent to behave as if permissions changed mid-goal, attempt unnecessary escalation, or misdiagnose local tool failures as repo/task failures.

Environment

  • Product: Codex Desktop
  • OS: Windows / MacOS
  • Workspace type: local Unity project
  • Goal mode: active persistent goal / goal continuation
  • Date observed: 2026-06-15
  • Exact Codex version: 26.609.41114

What happened

During an active goal, the session initially had an unrestricted profile:

sandbox_mode: danger-full-access
approval policy: never
filesystem: unrestricted

After Codex restarted/resumed the goal, the next injected environment context changed to a restricted profile with escalation rules:

sandbox_mode: workspace-write
filesystem: restricted
approval/escalation rules enabled

Immediately after that, ordinary local read-only commands such as rg and cm status failed with a Windows sandbox initialization error:

windows sandbox: helper_unknown_error: setup refresh had errors

The assistant then interpreted the resumed session as having a stricter sandbox and attempted escalation. Shortly after another interruption/restart, the environment context returned to the original unrestricted profile:

sandbox_mode: danger-full-access
approval policy: never
filesystem: unrestricted

Expected behavior

When a persistent goal is resumed after a Codex restart/update, the resumed session should preserve the same effective sandbox and approval policy as the pre-restart session, unless the user or app explicitly changes it.

If the environment must change, Codex should make that transition explicit and stable so the agent can reason correctly.

Actual behavior

The resumed goal received a different permission profile mid-goal. This created a misleading state where:

  • the agent believed the sandbox had become stricter,
  • local read-only commands failed due to sandbox setup,
  • escalation was attempted even though the original and later restored policy was approval: never,
  • progress was interrupted by environment mismatch rather than task logic.

Why this matters

Long-running goals depend on continuity. Permission-profile drift during goal continuation can cause:

  • incorrect diagnosis of failures,
  • unnecessary escalation requests,
  • aborted or delayed repo work,
  • inconsistent behavior across turns,
  • user confusion because the agent reports a sandbox change that the user did not request.

Reproduction outline

  1. Start Codex Desktop on Windows.
  2. Open a local workspace.
  3. Begin a long-running persistent goal.
  4. Observe initial environment context using unrestricted/full-access permissions.
  5. Let Codex restart/update.
  6. Observe that the resumed turn may inject a different sandbox/approval profile.
  7. Run local read-only commands such as rg or VCS status.
  8. Commands may fail with sandbox setup errors, and the agent may attempt escalation.

Suggested investigation areas

  • Persist the original sandbox/approval profile across goal-continuation resumes.
  • Ensure restart/update rehydration does not replace the active thread’s permission profile with a default or managed profile.
  • If permission profile changes are intentional, surface an explicit system event explaining the change.
  • Improve Windows sandbox helper failure reporting so setup failures are distinguishable from repo/tool failures.
  • Prevent escalation attempts when the latest effective policy is approval: never.

Notes

This is filed as a bug report per the repository contribution guidance, rather than as a pull request.

View original on GitHub ↗

5 Comments

itsonlyfrans · 2 months ago

Additional context: this does not appear to be isolated to the current Codex version/build. The permission-profile drift during goal continuation has been observed for a while across prior sessions/versions, especially when Codex restarts in the middle of a long-running goal.

wgu9 · 2 months ago

I looked through the current open-source core/session resume path to see where this could happen. The Windows/Desktop restart part may still live outside this repository, but the core code shows one important gap: persisted TurnContextItems contain the effective permission fields, while the reconstructed PreviousTurnSettings baseline currently drops them.

Relevant code paths:

  • codex-rs/protocol/src/protocol.rs: TurnContextItem includes approval_policy, sandbox_policy, and permission_profile.
  • codex-rs/core/src/session/turn_context.rs: TurnContext::to_turn_context_item() writes those permission fields into the persisted rollout turn context.
  • codex-rs/core/src/session/mod.rs: record_context_updates_and_set_reference_context_item() persists one RolloutItem::TurnContext per real user turn so resume/lazy replay can recover the latest durable baseline.
  • codex-rs/core/src/session/rollout_reconstruction.rs: when replay sees a RolloutItem::TurnContext, it only reconstructs PreviousTurnSettings { model, comp_hash, realtime_active }. The persisted approval_policy, sandbox_policy, and permission_profile are not carried into PreviousTurnSettings.
  • codex-rs/core/src/thread_manager.rs: resume_thread_from_rollout() resumes with the Config passed in by the caller. If the Desktop/app resume path passes a default or newly managed permission config after restart, core resume does not appear to have a prior permission baseline to compare against.
  • codex-rs/core/src/session/mod.rs: permission instructions for the next turn are rendered from the current turn_context.permission_profile and turn_context.approval_policy, so a resumed turn built from a different app-level config can present a changed sandbox/approval state to the model.

That makes the reported symptom plausible: the rollout has enough data to know the previous effective permissions, but the reconstructed resume baseline does not currently preserve/use those fields for continuity checks.

A minimal fix could be one of these:

  1. Extend the reconstructed previous-turn/session baseline to include effective approval_policy, sandbox_policy, and permission_profile, then detect any resume-time drift before the next turn starts.
  2. If permission changes on resume are intentional, emit an explicit event / environment-context note saying the effective permissions changed from X to Y and why.
  3. For goal continuation specifically, prefer the persisted thread permission profile over a fresh app default unless the user/app explicitly changes it.

Suggested regression test shape:

  1. Create a rollout with a persisted TurnContextItem using approval_policy = never and permission_profile = Disabled / full access.
  2. Resume the thread through resume_thread_from_rollout() or the app-server/goal-continuation path with a default restricted config.
  3. Assert either that the resumed session preserves the prior effective permission profile, or that a deterministic permission-change event is emitted before any model/tool turn can misinterpret the environment.
  4. On Windows, add a separate assertion that sandbox helper setup failures are surfaced as sandbox setup failures, not as repo command failures.

I would treat this as high severity for persistent goals because permission drift changes the agent's operational contract mid-task and can cause unnecessary escalation or incorrect diagnosis even for read-only commands.

Necmttn · 2 months ago

The permission profile should be persisted and compared at every goal continuation boundary.

Store sandbox mode, approval policy, writable roots, network setting, provider surface, and config source with the session. On resume, if the injected environment differs, show the diff and provenance before the agent runs another step.

That prevents the model from treating runtime drift as a task or repo failure.

---

_Generated with ax._

itsonlyfrans · 1 month ago

This is still happening on MacOS/Windows.

adamgwinn · 1 month ago

I reproduced this on Windows with additional permission-provenance evidence.

Environment

  • Codex Desktop package: 26.721.4979.0
  • App-server version after restart: 0.146.0-alpha.3.1
  • Affected thread: 019f5571-616e-7721-9e5f-268dc2be8fc2
  • Expected profile: Full Access

Observed restart sequence

Before the restart, the affected thread’s recent turn_context records consistently contained:

approval_policy=never
approvals_reviewer=user
sandbox_policy=danger-full-access
permission_profile=disabled

The previous app process disconnected at 2026-07-29T04:27:25Z, and the new app launched at 2026-07-29T04:27:48Z.

When this thread resumed at 2026-07-29T04:35:48Z, the Desktop log reported:

activePermissionProfileSource=default
derivedActivePermissionProfile=null
derivedApprovalPolicy=on-request
derivedApprovalsReviewer=user
derivedSandboxPolicy=workspaceWrite

hasCurrentPermissions=false
hasExplicitPermissions=false
hasLatestThreadSettings=false
hasLatestTurnParams=false

requestApprovalPolicy=null
requestApprovalsReviewer=null
requestPermissionProfile=null
requestSandboxMode=null

responseApprovalPolicy=on-request
responseApprovalsReviewer=user
responseSandboxPolicy=workspaceWrite

latestTurnStatus=interrupted
shouldSendPermissions=false
turnCount=5

The first post-restart turn_context, at 2026-07-29T04:36:13Z, consequently contained:

approval_policy=on-request
approvals_reviewer=user
sandbox_policy=workspace-write
permission_profile=managed

The UI still displayed Full Access. This was not Auto Review: the effective reviewer was user.

An ordinary shell command then failed with:

windows sandbox: CreateProcessAsUserW failed: 5 (Access is denied.)

The agent retried with sandbox_permissions=require_escalated, resulting in a manual approval notification.

I manually changed the UI permission away from Full and back to Full, then sent a small prompt to force a new turn. The new turn_context at 2026-07-29T04:37:54Z correctly returned to:

approval_policy=never
approvals_reviewer=user
sandbox_policy=danger-full-access
permission_profile=disabled

Ordinary shell execution then succeeded. Changing the UI setting did not repair the already-running downgraded turn; a new user turn was required.

Evidence this is not project-specific

A separate Full Access thread in a different local workspace had persisted state before the same restart:

sandbox_policy={"type":"disabled"}
approval_mode=never
updated_at=2026-07-29T03:01:23Z

Its pre-restart turn_context records were also never / danger-full-access. Nevertheless, its resume at 2026-07-29T04:31:08Z reported the same failure signature:

hasCurrentPermissions=false
hasExplicitPermissions=false
hasLatestThreadSettings=false
hasLatestTurnParams=false
activePermissionProfileSource=default
derivedApprovalPolicy=on-request
derivedApprovalsReviewer=user
derivedSandboxPolicy=workspaceWrite

Across recent local logs, I found 83 maybe_resume_success events involving 23 threads and six workspace-root groups. Fifty-six had no current, explicit, latest-thread, or latest-turn permission data and therefore used the default on-request/user profile. Many of those threads may legitimately have used the default profile, so this is evidence of the fallback mechanism—not 56 confirmed Full Access regressions.

Some resumes correctly preserved Full or Auto Review when hasCurrentPermissions=true, hasLatestThreadSettings=true, and the active source was thread_settings. The defect therefore appears intermittent and tied to missing permission state during resume rehydration.

The strongest local finding is that persisted Full Access state survived the restart, while thread/resume still returned or derived the default profile. The UI and resumed runtime consequently disagreed.

I can provide a bounded, redacted extract of the relevant Desktop log and turn_context records, or submit the diagnostic logs through /feedback, if that would help.