Repository navigation
fix(wsl): a passive restore never starts a stopped WSL distro; its bridge panes wait with Start - #35813
Conversation
…idge panes wait with Start
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 5 minutes. View limit details
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. Comment |
[Medium impact] The change affects restored WSL bridge terminals in Orca. The PR should not merge yet because unrelated floating-panel clicks can authorize a distro start, and Start can release panes from different distros. Findings
SummaryThe PR keeps passive WSL terminal restores from starting a distro and adds a shared Start card.
|
| function activateRealTab(activate: () => void): void { | ||
| clearClientHostedBrowserRowSelection() | ||
| // Picking a tab is the user acting on its workspace: its restore may start a stopped distro. | ||
| noteUserWorkspaceSwitch(useAppStore.getState().activeWorktreeId) |
There was a problem hiding this comment.
Floating clicks mark another workspace
Clicking a tab in the floating panel records a switch for the main window's workspace instead of the floating workspace. If the main workspace's WSL distro stopped earlier, a later automatic remount receives this unrelated click's timestamp and can start that distro without the user opening it. Use latest.current.props.worktreeId, which already identifies the clicked tab's workspace.
| noteUserWorkspaceSwitch(useAppStore.getState().activeWorktreeId) | |
| noteUserWorkspaceSwitch(latest.current.props.worktreeId) |
| lastSwitchAt.set(workspaceKey, Date.now()) | ||
| switchCounts.set(workspaceKey, (switchCounts.get(workspaceKey) ?? 0) + 1) |
There was a problem hiding this comment.
Picking the tab leaves it held
Picking an already-selected held terminal tab only updates these maps. The unchanged selection leaves the pane mounted, so no new connection reads the timestamp. Only Start releases the existing hold. Retry the held pane on explicit terminal selection too, so users do not need an extra Start click.
| if (start === -1) { | ||
| return null | ||
| } | ||
| const distro = message.slice(start + WSL_DISTRO_STOPPED_HOLD_ERROR.length + 1).split(/\s/)[0] |
There was a problem hiding this comment.
Start groups different distros together
The parser cuts distro names at the first space, so Ubuntu Test becomes Ubuntu. This changes the card label and the group released by Start. If both distros have held panes, pressing Start for either releases both groups and can start the other distro too. Encode the distro name in the error message and decode it without losing spaces.
… once; an unanswered read holds too
| if (state.activeView !== 'terminal') { | ||
| state.setActiveView('terminal') | ||
| } | ||
| noteUserWorkspaceOpen(worktreeId, opts?.navigationIntent) |
There was a problem hiding this comment.
Cmd+J selection still needs Start
Requiring navigationIntent: 'user-open' misses terminal selections from Cmd+J. activateWorkspaceTabPaletteResult calls activateAndRevealWorkspace without that option, then selects the terminal directly without using the tab-bar handler. Opening a restored terminal in a stopped WSL distro therefore shows Start instead of starting it as the user's explicit selection. Pass the intent from this user-driven path.
A passive restore or remount reaches launch preparation too (#35813), so it no longer runs as a user act; a stopped distro skips the install and the relay installs once the distro runs. A skipped WSL home now returns not_installed instead of falling through to the host writer, which would have written the WSL home over UNC.
A passive restore or remount reaches launch preparation too (#35813), so it no longer runs as a user act; a stopped distro skips the install and the relay installs once the distro runs. A skipped WSL home now returns not_installed instead of falling through to the host writer, which would have written the WSL home over UNC.
… distro Every start main makes through the runtime controller is background work unless its caller names the user's act (CLI and window creates and splits, paired-client creates and tab opens, agent launches and sessions, conversation resumes). #35813's hold now also holds those fresh and restored starts while the distro is stopped or unverifiable.
…stopped distro (E9) (#35827) * fix(wsl): Codex WSL hook installs and host hook checks never start a stopped distro (E9) (a) installManagedHooksIntoWslRuntime checks the settled running gate inside its trust queue right before the first UNC read, and again before the post-hash fallback trust write; a later reconcile checks before revoking or reinstalling. A stopped or unverifiable distro skips (null, as with no WSL plan). The canonical-path probe runs through process-host behind blockedWslDistroForExec; a refused probe settles as unavailable and keeps the known identity. The guest shell preflight and a pane launch's home preparation run as user acts on their distro. (b) repo.hooksCheck takes an optional background flag (older hosts ignore it); the renderer sends it, and the host refuses a stopped distro's background check the same way the desktop's hooks:check does. * fix(wsl): a pane launch is not a user act for the Codex hook install A passive restore or remount reaches launch preparation too (#35813), so it no longer runs as a user act; a stopped distro skips the install and the relay installs once the distro runs. A skipped WSL home now returns not_installed instead of falling through to the host writer, which would have written the WSL home over UNC. * fix(wsl): a fallback grant skipped mid-install reports trust pending hooks.json is written before the hash wait, so a skip after it is not a clean skip: return an error status saying the trust is pending instead of null. * chore: drop the stale process-host direct-import baseline entry
… distro Every start main makes through the runtime controller is background work unless its caller names the user's act (CLI and window creates and splits, paired-client creates and tab opens, agent launches and sessions, conversation resumes). #35813's hold now also holds those fresh and restored starts while the distro is stopped or unverifiable.
… distro (#35865) * fix(wsl): main's background terminal starts never start a stopped WSL distro Every start main makes through the runtime controller is background work unless its caller names the user's act (CLI and window creates and splits, paired-client creates and tab opens, agent launches and sessions, conversation resumes). #35813's hold now also holds those fresh and restored starts while the distro is stopped or unverifiable. * fix(wsl): headless Run now starts as the user's act; update create expectations A headless automation's Run now names the user's act through launchAgentTerminal, so it may start a stopped WSL distro; a scheduled run stays background and is held. The paired-client background create expectation now includes wslUserAct. * fix(wsl): main's background start checks a folder workspace's path passively With #35899's folder stat gate, the runtime preflight passes passiveWslRestore = !wslUserAct, so a background start never stats a stopped distro's folder as a user act before the daemon hold.
…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.
…tro (E9) (#35858) * fix(wsl): a passive restore's env prep never starts a stopped WSL distro (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. * fix(wsl): only a launch someone asked for acts on its distro; gate the 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.
ELI5
When Orca opens, reloads, or re-mounts a terminal on its own, a WSL bridge pane used to start its shell straight away. If that pane's WSL distro was stopped, starting the shell (
wsl.exe) booted the whole distro without you doing anything. Now such a pane shows its saved output and a " is stopped" card with Start. The distro starts only when you press Start, open New Terminal, or switch to the workspace yourself after it stopped.What Changed
wsl.exe, so the distro booted at launch, on reload, or when Orca re-mounted a pane, even after it had stopped.unverifiable) holds it too, as every other E9 gate does, and Start still recovers it.lib/workspace-switch-user-act.ts, shared with wsl-e9-preflight's wake fix. It records the user's own switches to a workspace: activations whose caller passesnavigationIntent: 'user-open'(sidebar, Cmd+J and the switcher, shortcuts, the task and PR pages), and picking a terminal tab.orca worktreeevent, the delete successor, launch restore, reloads and remounts.deferred-session-reattach-connect.ts) may start the distro only if it is this pane's first mount after such a switch, within 10 s of it. That usestakeWorkspaceSwitchWake(key, 'pane-restore:<pane>', { withinMs }), which is once per switch. Start also may. Every other restore sendswslPassiveRestore: true.DaemonPtyAdapterchecks before creating anything (daemon-wsl-passive-restore-hold.ts). A passive restore is held unlessreadWslDistroRunningState(..., { retryUnverifiable: true })saysrunning. The hold throwsterminal_held_wsl_distro_stopped:<distro>.wsl --shutdownaren't noted).holdForStoppedWslDistrokeeps the pane read-only.readNewerDaemonHeldScreen, which reads this machine's daemon history for the session).WslDistroStoppedHoldCardrenders the card. Start releases every pane held for that distro.WslDistroStoppedCardis the one stopped card. The host panes'WslHostStoppedCardnow renders through it, with the same copy and the same Start label, so there are no new strings.Why
Follow-up (not in this PR): a fresh terminal the CLI creates (
orca terminal create) in a workspace whose distro is stopped still starts it.Flag-off: applies to WSL bridge panes, which exist with
ORCA_INTERNAL_WSL_HOSTSunset too. A passive restore whose distro is confirmed stopped now waits for Start instead of booting the distro. Everything else is unchanged.User risk: low to medium. After a relaunch with the distro stopped, a bridge pane waits for Start instead of starting by itself. It's never held when the distro runs, or when the read is inconclusive.
Linked Issue
Internal: E9 launch auto-mount boot (found in verify-18 part 2), sequenced with #35733.
Visual Proof
N/A in this PR: the card is the existing stopped card (#35168), reused. A device check is planned for wsl-verify-7 part 3.
Testing
daemon-pty-adapter-wsl-passive-restore-hold.test.ts, realDaemonServer, win32 +\\wsl.localhostcwd:deferred-session-reattach-wsl-distro-stopped-hold.test.ts:workspace-switch-user-act.test.ts:markVisitedrecords only with'user-open';takeWorkspaceSwitchWakeis once per switch per consumer, and honourswithinMs.worktree-activation-automation-filter.test.ts: activations withoutnavigationIntent(the CLI event's and the delete successor's options) record nothing;'user-open'records one. A mutation that always records fails it.worktree-event-runtime-activation.test.tsandactive-worktree-removal-successor.test.ts: both callers pass nonavigationIntent.spawn-options-spawn-intent.test.ts: IPC forwards onlytrue.pnpm tc,oxlinton the changed files,check:code-quality:changedand the ast-grep gates pass.pty-connection-hidden-replay) failed under full load and passes alone (94/94).src/main/daemon,src/main/ipc/ptyand the gate tests pass 4618.Review
Terminal restore, so two reviewers: fix-reviewer and e9-reviewer.
Agent skill upstream boundary
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)