Repository navigation
[Windows][Desktop][Automations] Scheduled heartbeat drops the selected named permission profile and sends legacy sandboxPolicy instead #50120
Description
Summary
On Windows, a heartbeat automation targeting an existing local-project thread can replace the selected named permission profile with a generic approval mode.
My threads are configured to use the named permission profile youtube-pi-dev. After affected scheduled runs, the UI changes from that profile to the Japanese label 代わりに承認.
For one affected thread, the captured execution logs also show that:
active_permission_profiledisappears.- The recorded filesystem permission definition changes.
- An explicit write entry for the project directory is no longer present.
approval_policyandapprovals_reviewerremain unchanged.
A read-only investigation of the installed application's bundled JavaScript identified a likely cause: the heartbeat request-construction path receives the saved named profile but does not forward its ID. Instead, it explicitly sends the legacy sandboxPolicy to turn/start.
No proposed fix has been applied to the installed application. The original reproduction and the additional observation on October 2 occurred without applying the proposed patch.
Environment
- Operating system: Windows 11
- Installed app package:
OpenAI.Codex - Desktop app version inspected:
26.928.3736.0 - Application code collected: October 2, 2026, approximately 01:21 JST
- CLI version recorded in the affected thread's
session_meta:0.159.2 - Local project directory:
D:\program_works\python\youtube_pi_agent_gui - Named permission profile:
youtube-pi-dev - Profile inheritance:
:workspace - Approval policy in the compared records:
on-request - Approvals reviewer in the compared records:
auto_review - Automation type:
heartbeat, targeting an existing thread - Schedule for the directly investigated automation: every 30 minutes
The 0.159.2 value above comes from recorded session metadata. It is not a separately verified identification of every runtime process active during the investigation.
The project configuration contains default_permissions = "youtube-pi-dev", and the project is marked as trusted. The named profile is defined in the configuration and is present in normal/manual turn records.
Expected behavior
A heartbeat automation targeting a thread should preserve the named permission profile selected for that thread, including its identity and intended permissions, subject to applicable managed restrictions.
For a thread using youtube-pi-dev, the scheduled request should preserve that selection through permissions: "youtube-pi-dev" or an equivalent supported mechanism.
It should not silently replace the named profile with a legacy sandbox-policy representation that does not retain the profile identity and may produce a different permission definition.
Actual behavior and captured evidence
Persisted selection
At the time of capture, the desktop's heartbeat-thread-permissions-by-id state still contained the named profile for the affected thread, even though intervening execution records lacked it.
The following is the captured entry with the Windows username and thread ID redacted:
{
"activePermissionProfile": {
"id": "youtube-pi-dev",
"extends": ":workspace"
},
"approvalPolicy": "on-request",
"approvalsReviewer": "auto_review",
"sandboxPolicy": {
"type": "workspaceWrite",
"writableRoots": [
"C:\\Users\\<USER>\\.codex\\visualizations\\2026\\10\\01\\<THREAD_ID>"
],
"networkAccess": true,
"excludeTmpdirEnvVar": false,
"excludeSlashTmp": false
}
}This shows that the named selection was present in the captured desktop state. It does not establish that every in-memory state was identical at every execution boundary.
Execution-context comparison
The following two records come from the same affected thread on October 1, 2026. All times are JST, UTC+09:00.
| Field | 17:02:15 — named profile present | 17:16:41 — named profile absent |
|---|---|---|
active_permission_profile |
{"id":"youtube-pi-dev","extends":":workspace"} |
Field absent |
cwd |
D:\program_works\python\youtube_pi_agent_gui |
Same |
model |
gpt-6.1-sol |
Same |
approval_policy |
on-request |
Same |
approvals_reviewer |
auto_review |
Same |
| Explicit project-directory write entry in the detailed filesystem policy | Present | Absent |
Legacy sandbox_policy.writable_roots |
Visualization directory only | Same directory |
exclude_tmpdir_env_var |
false |
true |
exclude_slash_tmp |
false |
true |
The legacy writable_roots list was the same, but the complete legacy policy was not identical because the temporary-directory flags also changed.
Representative records from that thread show the following pattern:
2026-10-01 17:02:15 JST — active_permission_profile = youtube-pi-dev
2026-10-01 17:16:41 JST — active_permission_profile field absent
2026-10-01 17:17:25 JST — active_permission_profile = youtube-pi-dev
2026-10-01 17:46:41 JST — active_permission_profile field absent
2026-10-01 18:16:51 JST — active_permission_profile field absent
2026-10-01 18:21:09 JST — active_permission_profile = youtube-pi-dev
The recurring approximately 30-minute pattern is consistent with the automation schedule. However, not every record in this timeline has been individually correlated with a captured live request, so the timeline alone should not be treated as proof of the trigger for every entry.
Scope of the observed impact
The logs establish a change in the recorded filesystem permission definition, not an operating-system access test.
In particular:
- The loss of the explicit project write entry was recorded.
- Actual OS-level write denial was not tested as part of this investigation.
- The disappearance of an explicit read entry for the Python installation does not prove that Python became unreadable, because a broader root-read entry remained.
- Network enablement and per-domain authorization were not treated as equivalent. Loss of all configured domain permissions has not been demonstrated.
- No sandbox escape or privilege escalation has been demonstrated.
Reproduction pattern
This describes the pattern observed in my existing environment, not a verified clean-install minimal reproducer.
- Use a trusted local project with a valid named permission profile.
- Select that profile for an existing thread.
- Confirm that a normal/manual turn records the intended
active_permission_profile. - Configure a heartbeat automation targeting the same thread.
- Let the automation run on schedule.
- Compare the UI selection and the resulting
turn_contextwith the preceding named-profile turn.
Multiple threads in the same project have exhibited the UI symptom. Detailed before/after execution-context evidence was collected for one affected thread.
The exact conditions distinguishing an affected run from a run that retains the profile are not yet established. I am not claiming that every scheduled execution fails.
Additional observation on October 2: the previously unaffected main thread also changed
Initially, one older “main” thread appeared to retain youtube-pi-dev, while other threads exhibited the problem.
On the morning of October 2, 2026, JST, I also observed the permission selection change in that main thread.
At that time:
- The proposed application fix had not been applied.
- No post-fix real-device validation had been performed.
This is therefore an additional observation in an unpatched environment, not a regression caused by the proposed patch.
The main thread should no longer be described as an unaffected exception.
For this additional main-thread observation, the exact scheduled run, outgoing request, and resulting turn_context have not yet been correlated. I am reporting the observed selection change, not claiming that its detailed filesystem policy was separately verified.
The older main thread's captured working directory was under the previous C: location, whereas other affected threads used the D: location. This difference has not been established as the cause or a reliable workaround. The morning observation also shows that the older thread is not reliably protected from the symptom.
Technical investigation
The installed application's app.asar was read without modifying the installed application.
The investigation used the complete collected JavaScript modules and traced the heartbeat request path through final JSON serialization. This was an AI-assisted analysis, with an isolated request-packaging test described below.
1. The heartbeat entry point drops the named selection
File:
.vite/build/main-DkWgQSQe.js
Function: Jl
The following is the complete function, formatted for readability while retaining the bundled identifiers:
async function Jl({
statsig: e,
automation: t,
thread: n,
appServerConnection: r,
collaborationMode: i,
permissions: a,
waitForCompletion: o,
runTrigger: s
}) {
let c = await Yl({
thread: n,
appServerConnection: r
}),
l = a ?? await Zl({
appServerConnection: r,
cwd: c.cwd
}),
u = Ql(t);
await iu({
automation: t,
appServerConnection: r,
sessionId: c.sessionId,
threadId: c.id,
cwd: c.cwd,
prompt: u,
collaborationMode: i,
approvalPolicy: l.approvalPolicy,
approvalsReviewer: l.approvalsReviewer,
sandboxPolicy: l.sandboxPolicy,
model: null,
effort: null,
cyberAccessProgram: await Xl(r, c, e),
waitForCompletion: o,
runTrigger: s
});
}The incoming saved permissions object can contain activePermissionProfile, but Jl forwards only approvalPolicy, approvalsReviewer, and sandboxPolicy from that object.
It does not forward the profile ID.
It also does not forward runtimeWorkspaceRoots when that field is present.
2. The downstream heartbeat path does not restore the missing ID
In the same file:
iuconstructs the turn-start request withsandboxPolicy, without a namedpermissionsfield.au, used for the completion-waiting branch, forwards that request without restoring the named profile.
The traced connection-side path continues through:
.vite/build/main-DkWgQSQe.js
Jl
-> iu / au
.vite/build/bootstrap-DvSEn4qy.js
TN.startTurn
-> mM.startTurn
-> TN.sendInternalRequest
-> Rj.sendMessage
-> JSON serialization / transport
The metadata and compatibility transformations on this path did not add the missing profile ID.
This is distinct from the ordinary message-submission path involving $ue.startTurn, eue, and Yle / prepare. The existence of settings inheritance on that ordinary path does not establish that the heartbeat request receives the same supplementation.
3. The bundle already contains a named-profile/legacy-policy conversion helper
File:
.vite/build/src-ghAWefM3.js
Function: qn, exported as pa
Complete function:
function qn(e) {
return e.activePermissionProfile == null
? { sandboxPolicy: e.sandboxPolicy }
: { permissions: e.activePermissionProfile.id };
}This helper sends the profile ID when a named selection exists, and the legacy sandbox policy otherwise.
The investigated heartbeat Jl -> iu path does not perform this distinction.
4. Comparison with the public runtime source
The public rust-v0.159.2 tag was inspected because it matches the version string recorded in the affected thread's session metadata.
The relevant source behavior is:
TurnRequestProcessor::build_thread_settings_overridesrejects simultaneouspermissionsandsandboxPolicy.- When
permissionsis supplied, the named profile is resolved using the thread configuration and working directory. SessionConfiguration::applyhas a separate legacy-policy path when asandbox_policyoverride is supplied without apermission_profile.- That path calls
set_legacy_permission_profile, which installs a legacy permission-profile snapshot.
References:
- https://github.com/openai/codex/blob/rust-v0.159.2/codex-rs/app-server/src/request_processors/turn_processor.rs
- https://github.com/openai/codex/blob/rust-v0.159.2/codex-rs/core/src/session/session.rs
- https://github.com/openai/codex/blob/rust-v0.159.2/codex-rs/core/src/config/resolved_permission_profile.rs
The running native binary was not compared byte-for-byte with that public source version. This comparison supports the diagnosis but is not a complete verification of every runtime behavior on the affected machine.
Isolated request-construction tests
An isolated test harness used the extracted application functions, with thread-resume responses, transport, notifications, and related dependencies replaced by test doubles.
The investigation compared:
- 32 cases using the original request-construction code.
- 32 cases using a proposed correction.
All 64 cases passed their respective assertions. For the original-code cases, those assertions included confirming the missing profile ID; they do not mean that the original behavior was correct.
The combinations covered scheduled versus “run now” invocation, mocked compatibility branches, the presence or absence of runtime workspace roots, and named-profile versus legacy inputs.
Representative results:
| Input | Original outgoing request | Proposed correction |
|---|---|---|
Main-thread-equivalent saved state with youtube-pi-dev |
No named permissions; legacy sandboxPolicy present |
permissions: "youtube-pi-dev"; no sandboxPolicy |
Affected-thread-equivalent saved state with youtube-pi-dev |
No named permissions; legacy sandboxPolicy present |
permissions: "youtube-pi-dev"; no sandboxPolicy |
Built-in named selection :workspace |
No named permissions; legacy sandboxPolicy present |
permissions: ":workspace"; no sandboxPolicy |
| No named profile | Legacy sandboxPolicy |
Legacy behavior retained |
These tests validate request packaging only. They do not constitute a Windows application reproduction, a test of the native runtime, an OS sandbox test, or proof that the proposed fix resolves every execution condition.
Suggested fix direction
Preserve the selected named profile throughout heartbeat request construction.
When a valid saved activePermissionProfile exists, pass its ID using permissions and do not simultaneously send sandboxPolicy.
When no named profile exists, retain the appropriate legacy behavior.
Please also review:
- The fallback path when saved thread permissions are unavailable.
- Cold-resume and existing-thread state interactions.
- Whether existing
runtimeWorkspaceRootsneed to be preserved without replacing an absent value with an empty list. - Scheduled and “run now” execution, as well as regression coverage for ordinary manual turns.
The correction should retain the existing approval policy, approvals reviewer, model settings, input, notification behavior, completion handling, and applicable restrictions.
The proposed local correction targets Jl and iu, but it has not been applied to the installed application and has not received real-device validation.
Diagnostic material and current status
Available diagnostic material includes:
- The relevant original bundled JavaScript from app version
26.928.3736.0. - Captured persisted permission-state entries.
- Before/after execution-context records and a permission timeline.
- A proposed correction.
- The isolated request-packaging test results.
Redacted supporting material can be provided through an appropriate support channel.
Current status:
- Request-construction omission identified in the inspected application code.
- Detailed permission-definition changes recorded for an affected thread.
- Additional UI occurrence observed in the previously unaffected main thread.
- No proposed fix applied to the installed application.
- No post-fix real-device or scheduled-run validation completed.
Activity
karappo-kara commentedon Oct 2, 2026
Update: temporary local workaround and limited live validation
The issue also reproduced after upgrading to desktop app version 26.928.4866.0. I then tested a local correction, with the following stages kept separate:
Code inspection: The updated heartbeat request path still received saved named-profile information but did not forward the selected profile ID, and supplied the legacy
sandboxPolicytoturn/start.Correction candidate: The revised code forwards the selected profile ID through the existing named-permission path and omits the legacy sandbox policy when a named profile is selected. The legacy fallback remains available when no named profile is selected. Approval policy and approval reviewer were preserved.
Local application: The correction was applied to a separate application copy. The original Microsoft Store installation and WindowsApps contents were left unchanged, and backups were retained. The modified copy no longer has a valid original Authenticode signature; signature verification and application integrity enforcement were not disabled.
Live validation: A manually triggered scheduled run retained the selected profile, although that run was interrupted. A separate, naturally scheduled run completed. Its native execution record retained
active_permission_profileand the recorded detailed permission settings, including the intended project write access and enabled network access. The temporary test schedule was restored to its original content and paused state.These tests confirm profile and recorded permission retention for the tested cases. The natural-run test used no tools, so it did not independently exercise filesystem writes or network connectivity. Other profiles and all scheduling paths have not been validated.
A later browser operation encountered a sandbox setup refresh error. Its relationship to this local correction is unknown.
This remains an experimental local workaround pending an official fix. The issue should remain open.
aetherxeg-source commentedon Oct 2, 2026
A deeper reason to preserve the named profile here is:
effective policy snapshot != authorization-policy identityTwo named profiles can resolve to the same filesystem/network policy at one instant and still represent different authorization contracts. If an automation flattens the selected profile into a legacy snapshot, later managed-policy or profile changes can no longer be resolved against the identity the user actually selected.
I would carry both:
rather than treating the resolved sandbox fields as a substitute for the profile identity.
A useful conformance test would create two profiles P1/P2 that initially resolve to identical effective permissions, schedule a run under P1, then change a managed rule that makes P1 and P2 diverge before execution. The scheduled run should resolve and record P1 at execution time (or explicitly reject a stale profile contract), never continue from an indistinguishable flattened snapshot.
That makes the invariant explicit:
same permissions now != same authorization semantics over time.karappo-kara commentedon Oct 2, 2026
Thanks for the suggestion. The workaround already preserves the selected profile ID and passes it through the existing named-profile resolution path. The contents of the selected youtube-pi-dev profile itself were not changed as part of this workaround; the change was limited to preserving and forwarding the profile identity through the scheduled-run path. I have not tested policy changes between scheduling and execution yet. I see generation/digest tracking as a separate design consideration beyond this targeted forwarding fix.
karappo-kara commentedon Oct 4, 2026
``
Update: profile identity loss still reproduces after the Microsoft Store update
I completely closed the desktop application, updated it through Microsoft Store, rebooted Windows, and launched the normal Store-installed application. This test did not use the separately modified application copy described in my previous comment.
Application-reported version information:
Before this update: 26.928.40906, build 12694, release channel
prodAfter this update: 26.930.31730, build 12947, release channel
prodOn October 4, 2026, I compared a manual turn at 10:51:07 JST with one naturally scheduled heartbeat turn at 11:14:15 JST, in the same existing thread. The heartbeat input and application turn were correlated with the matching turn ID in the native execution record.
Recorded setting | Manual turn | Naturally scheduled heartbeat -- | -- | -- Selected named profile | youtube-pi-dev, extending :workspace | Selected named profile not recorded Explicit write permission for the D: project directory | Present | Present Network setting | Enabled | Enabled approval_policy | on-request | on-request approvals_reviewer | auto_review | auto_review exclude_tmpdir_env_var / exclude_slash_tmp | false / false | true / trueThe named-profile retention failure still reproduced. However, unlike the earlier detailed reproduction, the explicit D: project write permission remained present in this run.
The UI changed from
youtube-pi-devto 「承認を依頼」 (“Ask for approval”). The earlier report showed 「代わりに承認」 (“Approve for me”). Despite this difference in the displayed label, the recorded approval policy and reviewer remained unchanged. The complete filesystem permission definition was also not identical, so this cannot be described as only a cosmetic change.Separate manual checks of the in-app browser, PowerShell-to-Python environment-variable inheritance, ordinary temporary-file operations, and fresh Node startup succeeded. These are separate execution paths and do not establish that scheduled runs preserve the selected permission profile.
This scheduled test inspected recorded permissions; it did not exercise project writes or network connectivity. I have not inspected the updated application's request-construction code, so I cannot yet determine whether the differences reflect a partial fix, different saved state, or another cause. I also have not established whether the generic UI label results from newly introduced initialization behavior.
The temporary test schedule was restored to its original configuration and paused state. After recording the failure, I manually reselected
youtube-pi-dev; that reselection is not evidence of a scheduled-run fix.The issue remains reproducible in the tested normal Store installation and should remain open.
info618 commentedon Oct 11, 2026
Additional macOS reproduction of this natural-heartbeat profile loss, matching the request-construction behavior reported here.
Environment: macOS / Mac mini; ChatGPT Desktop 26.1007.30912, build 20156, prod. Bundled Codex version 0.162.0-alpha.17.2. The configured named profile is
b2m-hub, extending:workspace, withon-requestandauto_review. Both retained projects explicitly select this profile; no legacysandbox_modeorsandbox_workspace_writeis present in the inspected user/project configuration layers. The installed updater offers no newer release.On October 11, 2026, two existing local chats showed:
b2m-hubauto_reviewon-requestauto_reviewon-requestauto_reviewon-requestEach natural run was correlated to its heartbeat input and native turn ID. No tools were executed in those natural runs. The tester's explicit retained-checkout root write grant disappeared, while network access remained enabled. Settings and timer files were byte-identical across the compared runs. These are recorded-permission results, not additional filesystem/network tests under the faulty timer contexts.
Read-only inspection of the installed Desktop bundle found that
Glreceives saved permissions but passes approval policy, reviewer and legacysandboxPolicytotu;tudoes not forward the selectedactivePermissionProfile.idthrough namedpermissions. No application or persisted-state patch was applied.Please provide an official Desktop correction preserving the selected named profile on natural heartbeat requests, using the named
permissionsfield without also supplying legacysandboxPolicy, while retaining approval reviewer, workspace roots and existing chat/worktree/schedule identities. The configuration allowlist is not a demonstrated substitute for forwarding the missing request field. We need a supported fix, not a modified application copy or alternate executor.Private account/contact information, usernames, chat IDs and local paths are omitted from this public reproduction.