You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(wsl): a passive restore's env prep never starts a stopped WSL distro (E9) - #35858
#35813 stops a terminal that Orca restores by itself from starting a stopped WSL Linux. But before that check runs, Orca prepares the terminal's environment, and for Codex that preparation already reached into the distro. It looked up the Linux home folder, copied config files, ran WSL scripts and started a history bridge. Any of those started the distro before #35813 could hold the pane. Now a restore nobody asked for does none of that on a stopped distro. Opening a terminal yourself, or pressing Start, still does all of it.
What Changed
Before:resolvePtySpawnHostEnv runs for every WSL PTY spawn that isn't preAdopted, including passive restores, before #35813's DaemonPtyAdapter hold. In it:
prepareForCodexLaunchAsync (WSL targets):
getWslHome, a synchronouswsl.exe probe that blocks main for up to 5 s, run whenever the home isn't cached;
the legacy auth drain (runWslProcess / readWslCodexAuths, which is wsl.exe);
the config and instructions sync (mkdirSync and copies on \\wsl.localhost);
the WSL session bridge (wsl.exe).
ensureCodexStateDbBackfillRecoveryStarted, which reads the home's state DB over UNC.
awaitExplicitAgentPluginGuestReadiness, which ensures the hook relay (wsl.exe) for pi, omp and Claude launches.
Each of these started a stopped distro before the hold could act.
After:
Spawn path: a WSL launch someone asked for runs its env prep as a user act on its distro, via runAsWslLaunchUserAct. If no distro is named, that means the default distro. A passive restore (wslPassiveRestore, from fix(wsl): a passive restore never starts a stopped WSL distro; its bridge panes wait with Start #35813) and a non-WSL launch never run as a user act, not even on the default distro, so their prep goes through the E9 gates. This applies in both places env prep happens:
resolvePtySpawnHostEnv (the daemon and runtime path);
the daemon-off local provider's buildSpawnEnv, which now receives wslPassiveRestore from the spawn options.
prepareForCodexLaunchAsync: for a WSL target it checks blockedWslDistroForExec once. When the distro is blocked (stopped or unverifiable), it skips the probe, drain, sync and bridge, and returns the home without probing:
an account home comes from settings and the recorded identity;
system default comes from the cached guest home, or null when uncached. null means the guest's own ~/.codex, which is the system default, so the identity is still correct.
The 5 s sync probe: when not blocked, the guest home is warmed with getWslHomeAsync first, so the synchronous getWslHome reads after it hit the cache instead of blocking main.
State-DB recovery: it starts only if blockedWslDistroForPath(home) passes.
Plugin readiness: it returns false (not ready) without ensuring the relay when blockedWslDistroForExec(distro) blocks.
Hook relay start: every WSL spawn with agent hooks on calls wslHookRelayManager.ensureForDistro from buildPtyHostEnv, and starting a relay is a wsl.exe launch. ensureInternal now checks blockedForExec before its state check, so concurrent ensures still join one start, and a blocked distro gets no relay. Explicit launches pass the check through the user act.
Claude and OMP env prep: checked. For WSL, the OMP prep only copies env keys, and Claude's readyForLaunch and the Antigravity prep both return early; none of them touch UNC. The relay ensure above is the only WSL touch on that path.
Codex resume prep: traced; no change needed. The desktop's prepareCodexSessionResumeForLaunch returns null for WSL targets before any I/O. The orcad variant runs inside the distro.
Why
A held restore never spawns, and the user's Start re-runs the launch as a user act, which does the skipped work then. So the gate only removes work that would have booted the distro for a pane that #35813 holds anyway.
Gating at a single entry (prepareForCodexLaunchAsync), rather than inside each of getWslHome, the drain, the sync and the bridge, keeps those helpers unchanged for their other callers.
The user-act scope also covers the Codex hook install's follow-up from #35827: a genuine user launch now runs prepareRuntimeHomeForLaunch inside the act, so its hooks install before spawn. A passive restore still doesn't.
Linked Issue
Internal: E9, the env-prep booter in front of #35813's hold. Sequenced before #35733.
runtime-home-wsl-passive-restore-prep.test.ts, using the real rule (blocked unless the distro is the current user act):
a passive restore gets the account home with no getWslHome/getWslHomeAsync, drain, sync or bridge call;
system default with an uncached home returns null without probing;
the same launch as a user act warms the home asynchronously and drains.
spawn-host-env-wsl-passive-restore.test.ts, with listWslDistrosAsync mocked to ['Ubuntu']:
an explicit launch runs as a user act on its distro, and a passive restore doesn't;
a passive restore with no named distro is never a user act on the default;
a host-target spawn never is either;
an explicit WSL launch with no named distro acts on the default;
a stopped distro skips the state-DB recovery, while a running one still starts it.
wsl-agent-plugin-readiness.test.ts: a stopped distro's relay isn't ensured, and a running one is.
wsl-background-exec-gate.test.ts: runAsWslLaunchUserAct acts on the named or default distro only for an asked-for WSL launch, never for a passive restore or a non-WSL launch.
wsl-hook-relay-manager.test.ts: a blocked distro gets no relay.
local-pty-pi-owner-env.test.ts: the daemon-off env builder receives wslPassiveRestore.
Existing WSL account suites now mock the exec gate as passing (their launches are user acts) and the async home probe. The drain-ordering test waits for the drain instead of assuming one microtask.
pnpm run typecheck:ci, oxlint, oxfmt and check:code-quality:changed pass. codex-accounts, ipc/pty, daemon, agent-hooks, codex, startup and providers pass (10,575 before the rebase; 5,095 in the touched suites after).
Review
Agent skill upstream boundary
Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.
The changes add a WSL launch-user gate and pass passive-restore status through PTY environment preparation. They prevent Codex home probing and selected background preparation when WSL execution is blocked, and skip state DB recovery for blocked homes. Agent-plugin readiness and relay setup now check whether a distro is blocked before proceeding. Tests cover these paths and add asynchronous WSL home mocks to existing tests.
Priority: Normal
Merge Risk: Moderate · up to 2f78c
Starting an agent through a paired WSL environment can skip Codex authentication and resource preparation when the distro is stopped. Fix that launch path before merging.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta…
Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks: 4 passed
Check name
Status
Explanation
Title check
The title names the main change: a passive restore's environment preparation no longer starts a stopped WSL distro. The changeset gates Codex launch prep, spawn env prep, state-DB recovery, plugin rea…
Description check
The description follows the template closely. It has ELI5, What Changed (before/after with mechanism), Why (including alternatives considered), Linked Issue, Visual Proof marked N/A with a reason, Tes…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Title check
Explanation
The title names the main change: a passive restore's environment preparation no longer starts a stopped WSL distro. The changeset gates Codex launch prep, spawn env prep, state-DB recovery, plugin readiness, and hook-relay startup behind a blocked-distro check and a user-act scope for passive restores. The title is specific, follows the conventional commit format, and names the ticket reference (E9). It omits the gating mechanism, which is acceptable for a title.
Full details: Description check
Explanation
The description follows the template closely. It has ELI5, What Changed (before/after with mechanism), Why (including alternatives considered), Linked Issue, Visual Proof marked N/A with a reason, Testing with checkboxes and specific test files, Review, Agent skill upstream boundary, and Notes. The Linked Issue section names an internal ticket (E9) and a sequencing dependency rather than a GitHub issue. The template requires a linked issue for outside contributors, and the author is presumably an internal contributor, so this is a minor gap. The AI Disclosure section was omitted, which the template allows for internal team members. The Checklist section is not filled in. The description is mostly complete, so the check passes.
Full details: Docstring Coverage
Explanation
Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 9 functions across 21 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
Finishing Touches: 💡 1
📝 Generate docstrings: 💡
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Stops passive WSL environment prep from starting a stopped distro while keeping explicit launches inside a user-act scope.
Requested WSL launches prepare their terminal inside a user-act scope.
Codex preparation skips guest work for a stopped WSL distro.
WSL agent-hook setup waits for a running distro before starting its relay.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[WSL environment prep] --> B{Explicit launch?}
B -->|Yes| C[Run inside user act]
B -->|No| D[Check distro state]
D -->|Stopped or unknown| E[Skip guest prep]
D -->|Running| F[Prepare guest environment]
C --> F
E --> G[Existing restore hold]
The reason will be displayed to describe this comment to others. Learn more.
Hook prep starts stopped distro
Returning a recorded account home here does not stop the caller's hook prep. createCodexRuntimeHomeLaunchPreparation passes that home to prepareRuntimeHomeForLaunch, which starts a wsl.exe path probe and reads the guest's hooks.json. A passive restore of a managed account on a stopped non-default distro therefore still starts it, even when this gate blocks correctly.
Carry the blocked result through to the caller and skip guest hook prep too.
The reason will be displayed to describe this comment to others. Learn more.
Failed warm-up still blocks desktop
If the async home probe fails, getWslHomeAsync returns null without caching it and clears its in-flight entry. The following home resolution and auth drain can then call getWslHome, which runs another blocking five-second probe. This leaves the desktop freeze that the warm-up is meant to remove.
Keep an unknown home from falling back to synchronous guest probes during this async launch path, and cover a failed warm-up in the tests.
…tro (E9)
resolvePtySpawnHostEnv runs before #35813's hold. Every spawn but a passive
restore now runs its prep as a user act on its distro, so explicit launches
are unchanged. prepareForCodexLaunchAsync checks blockedWslDistroForExec once
for WSL targets and, when blocked, skips the guest-home probe, the legacy auth
drain, the config and instructions sync and the session bridge, returning the
home without probing. When not blocked, the home is warmed with getWslHomeAsync
so the sync reads hit the cache instead of a 5 s wsl.exe. The state-DB
recovery and the explicit plugin readiness wait are gated the same way.
…e relay start
A passive restore or a non-WSL launch never runs as a user act, not even on
the default distro (a null distro resolved to the default before). The
daemon-off local provider applies the same rule, receiving wslPassiveRestore
from the spawn options. Starting the hook relay (ensureForDistro from every
WSL spawn's env build) now checks blockedForExec before the state check, so a
passive restore on a stopped distro starts no relay.
The reason will be displayed to describe this comment to others. Learn more.
Relay starts after hooks stop
If the user turns hooks off while blockedForExec is waiting, disposeAll({ permanent: false }) clears the relays but leaves this.disposed false. This continuation then starts a new relay without checking the hooks setting again. That relay escapes the disable cleanup and can keep WSL running.
Recheck isWslHookRelayAllowed(this.deps) after the wait, before creating state or starting a relay.
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.
Run the WSL account-environment preparation inside a user-act scope.
The runtime-environment call handler applies the account-environment shim to explicit launch requests, but it calls prepareForCodexLaunchAsync without a user-act scope. For a stopped WSL distro, the new guard therefore skips Codex authentication draining, resource sync, and session-bridge preparation for a user-triggered launch.
Wrap this boundary for WSL environments and preserve the existing unwrapped path for non-WSL environments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
#35813 stops a terminal that Orca restores by itself from starting a stopped WSL Linux. But before that check runs, Orca prepares the terminal's environment, and for Codex that preparation already reached into the distro. It looked up the Linux home folder, copied config files, ran WSL scripts and started a history bridge. Any of those started the distro before #35813 could hold the pane. Now a restore nobody asked for does none of that on a stopped distro. Opening a terminal yourself, or pressing Start, still does all of it.
What Changed
Before:
resolvePtySpawnHostEnvruns for every WSL PTY spawn that isn'tpreAdopted, including passive restores, before #35813'sDaemonPtyAdapterhold. In it:prepareForCodexLaunchAsync(WSL targets):getWslHome, a synchronouswsl.exeprobe that blocks main for up to 5 s, run whenever the home isn't cached;runWslProcess/readWslCodexAuths, which iswsl.exe);mkdirSyncand copies on\\wsl.localhost);wsl.exe).ensureCodexStateDbBackfillRecoveryStarted, which reads the home's state DB over UNC.awaitExplicitAgentPluginGuestReadiness, which ensures the hook relay (wsl.exe) for pi, omp and Claude launches.Each of these started a stopped distro before the hold could act.
After:
runAsWslLaunchUserAct. If no distro is named, that means the default distro. A passive restore (wslPassiveRestore, from fix(wsl): a passive restore never starts a stopped WSL distro; its bridge panes wait with Start #35813) and a non-WSL launch never run as a user act, not even on the default distro, so their prep goes through the E9 gates. This applies in both places env prep happens:resolvePtySpawnHostEnv(the daemon and runtime path);buildSpawnEnv, which now receiveswslPassiveRestorefrom the spawn options.prepareForCodexLaunchAsync: for a WSL target it checksblockedWslDistroForExeconce. When the distro is blocked (stopped or unverifiable), it skips the probe, drain, sync and bridge, and returns the home without probing:nullwhen uncached.nullmeans the guest's own~/.codex, which is the system default, so the identity is still correct.getWslHomeAsyncfirst, so the synchronousgetWslHomereads after it hit the cache instead of blocking main.blockedWslDistroForPath(home)passes.false(not ready) without ensuring the relay whenblockedWslDistroForExec(distro)blocks.wslHookRelayManager.ensureForDistrofrombuildPtyHostEnv, and starting a relay is awsl.exelaunch.ensureInternalnow checksblockedForExecbefore its state check, so concurrent ensures still join one start, and a blocked distro gets no relay. Explicit launches pass the check through the user act.readyForLaunchand the Antigravity prep both return early; none of them touch UNC. The relay ensure above is the only WSL touch on that path.prepareCodexSessionResumeForLaunchreturnsnullfor WSL targets before any I/O. The orcad variant runs inside the distro.Why
A held restore never spawns, and the user's Start re-runs the launch as a user act, which does the skipped work then. So the gate only removes work that would have booted the distro for a pane that #35813 holds anyway.
Gating at a single entry (
prepareForCodexLaunchAsync), rather than inside each ofgetWslHome, the drain, the sync and the bridge, keeps those helpers unchanged for their other callers.The user-act scope also covers the Codex hook install's follow-up from #35827: a genuine user launch now runs
prepareRuntimeHomeForLaunchinside the act, so its hooks install before spawn. A passive restore still doesn't.Linked Issue
Internal: E9, the env-prep booter in front of #35813's hold. Sequenced before #35733.
Visual Proof
N/A: no UI change; #35813's card shows as before.
Testing
runtime-home-wsl-passive-restore-prep.test.ts, using the real rule (blocked unless the distro is the current user act):getWslHome/getWslHomeAsync, drain, sync or bridge call;nullwithout probing;spawn-host-env-wsl-passive-restore.test.ts, withlistWslDistrosAsyncmocked to['Ubuntu']:wsl-agent-plugin-readiness.test.ts: a stopped distro's relay isn't ensured, and a running one is.wsl-background-exec-gate.test.ts:runAsWslLaunchUserActacts on the named or default distro only for an asked-for WSL launch, never for a passive restore or a non-WSL launch.wsl-hook-relay-manager.test.ts: a blocked distro gets no relay.local-pty-pi-owner-env.test.ts: the daemon-off env builder receiveswslPassiveRestore.pnpm run typecheck:ci, oxlint, oxfmt andcheck:code-quality:changedpass.codex-accounts,ipc/pty,daemon,agent-hooks,codex,startupandproviderspass (10,575 before the rebase; 5,095 in the touched suites after).Review
Agent skill upstream boundary
Notes
ORCA_INTERNAL_WSL_HOSTSunset, like fix(wsl): a passive restore never starts a stopped WSL distro; its bridge panes wait with Start #35813. Off Windows, and for non-WSL spawns, the gates answer "not blocked" with no probe, so nothing changes.