Repository navigation
feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) - #27819
Conversation
4eba961 to
6e18d3a
Compare
ca22e1a to
21a0166
Compare
6e18d3a to
cdb14ef
Compare
21a0166 to
ae08e94
Compare
cdb14ef to
0eaeacd
Compare
…d, so a /mnt/c shim cannot re-enter
…cord does not name
…as a WSL host SSH-managed orcad inside a distro and Orca under WSLg kept detecting drive-mounted CLIs before this stack; the setsid launch now marks itself with ORCA_WSL_ORCAD_HOST=1 and only that process filters.
… WSL host orcad On Windows the preflight env merges the persisted registry Path, which the fallback never searched before; only the marked WSL host runtime now passes a filtered PATH.
b7ce5f1 to
deaf720
Compare
|
Merge-train review (blast radius outside WSL): the /mnt/ PATH filter in agent detection/preflight was active in any WSL guest process (SSH-managed orcad inside a distro, Orca under WSLg). It now applies only to an orcad started by the WSL-host setsid launch ( |
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to
|
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
454c2521-9232-46d8-82f6-7004fb11067c
📒 Files selected for processing (28)
config/tsconfig.cli.jsonsrc/main/agent-hooks/local-agent-cli-presence.test.tssrc/main/agent-hooks/local-agent-cli-presence.tssrc/main/ipc/preflight-local-env.tssrc/main/ipc/wsl-guest-windows-drive-path.test.tssrc/main/ipc/wsl-guest-windows-drive-path.tssrc/main/orcad/orcad-wsl-guest-shell-path.test.tssrc/main/preflight/agent-command-detection.test.tssrc/main/preflight/agent-command-detection.tssrc/main/ssh/orcad-host-transport.tssrc/main/ssh/orcad-managed-runtime-context.tssrc/main/ssh/orcad-remote-context.tssrc/main/ssh/orcad-remote-launch-setsid.test.tssrc/main/ssh/orcad-remote-launch.tssrc/main/ssh/orcad-remote-runtime-control.tssrc/main/ssh/orcad-runtime-deployment.tssrc/main/wsl/wsl-distro-holder.test.tssrc/main/wsl/wsl-distro-holder.tssrc/main/wsl/wsl-distro-registry-id.test.tssrc/main/wsl/wsl-distro-registry-id.tssrc/main/wsl/wsl-orcad-deploy.test.tssrc/main/wsl/wsl-orcad-deploy.tssrc/main/wsl/wsl-orcad-host-transport.test.tssrc/main/wsl/wsl-orcad-host-transport.tssrc/main/wsl/wsl-orcad-instance-adoption.test.tssrc/main/wsl/wsl-orcad-instance-adoption.tssrc/shared/agent-cli-install-dir-fallback.test.tssrc/shared/local-agent-install-dir-detection.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.
| // Unreadable: keep PATH as is rather than guess which entries are Windows drives. | ||
| } | ||
| } | ||
| cachedGuestMounts = mounts |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '154,200p' src/main/agent-hooks/local-agent-cli-presence.ts
sed -n '15,48p' src/main/ipc/preflight-local-env.ts
sed -n '20,53p' src/main/preflight/agent-command-detection.ts
sed -n '69,91p' src/main/ipc/wsl-guest-windows-drive-path.tsRepository: stablyai/orca
Length of output: 4439
🏁 Script executed:
set -o pipefail
printf '%s\n' '--- local-agent-cli-presence imports and caller guards ---'
sed -n '1,80p' src/main/agent-hooks/local-agent-cli-presence.ts
sed -n '120,250p' src/main/agent-hooks/local-agent-cli-presence.ts
printf '%s\n' '--- preflight command detection complete relevant flow ---'
sed -n '1,130p' src/main/preflight/agent-command-detection.ts
printf '%s\n' '--- preflight local environment callers ---'
rg -n -F --glob '*.ts' -e 'buildLocalPreflightEnv' -e 'detectLocalManagedAgentCliPresence' -e 'detectCommandsInInstallDirs' -e 'isCommandOnPath' src/main
printf '%s\n' '--- definitions of path detection helpers ---'
rg -n -F --glob '*.ts' -e 'function isCommandOnPath' -e 'export async function isCommandOnPath' -e 'function detectCommandsInInstallDirs' -e 'export function detectCommandsInInstallDirs' src/mainRepository: stablyai/orca
Length of output: 15275
🏁 Script executed:
set -o pipefail
printf '%s\n' '--- managed-agent hook caller ---'
sed -n '120,185p' src/main/agent-hooks/managed-agent-hook-controls.ts
printf '%s\n' '--- preflight command execution helpers ---'
sed -n '1,95p' src/main/ipc/preflight-command-exec.ts
sed -n '220,270p' src/main/ipc/preflight-command-exec.ts
sed -n '145,185p' src/main/ipc/preflight-command-exec.ts
printf '%s\n' '--- install-directory fallback ---'
sed -n '1,220p' src/shared/local-agent-install-dir-detection.ts
printf '%s\n' '--- preflight WSL target resolution ---'
sed -n '1,180p' src/main/ipc/preflight-runtime-target.tsRepository: stablyai/orca
Length of output: 11902
Do not cache failed mount discovery.
When /proc/mounts cannot be read, readGuestWindowsDriveMounts() caches []. Later CLI detection and generic preflight then search an unfiltered PATH, so /mnt/<drive> entries can be treated as valid candidates. Skip caching on the failure path.
Suggested fix
--- "a/src/main/ipc/wsl-guest-windows-drive-path.ts"
+++ "b/src/main/ipc/wsl-guest-windows-drive-path.ts"
@@ -72,16 +72,20 @@
if (cachedGuestMounts) {
return cachedGuestMounts
}
let mounts: string[] = []
+ let cacheable = true
if (isWslHostOrcadProcess()) {
try {
mounts = parseWindowsDriveMountPoints(readFileSync('/proc/mounts', 'utf8'))
} catch {
- // Unreadable: keep PATH as is rather than guess which entries are Windows drives.
+ // Unreadable: keep PATH as is rather than guess; retry on the next call.
+ cacheable = false
}
}
- cachedGuestMounts = mounts
+ if (cacheable) {
+ cachedGuestMounts = mounts
+ }
return mounts
}
/** @internal - tests need a fresh mount read. */| if (deployed.outcome === 'already-active') { | ||
| // A stopped distro ended the active slot with it; start it again only on proven exit. | ||
| const wake = await wakeStoppedManagedOrcad(slot) | ||
| if (wake.outcome === 'recovery-refused') { | ||
| throw new Error(wake.reason) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
rg -nP -C3 "outcome:\s*'" src/main/ssh/orcad-managed-wake.tsRepository: stablyai/orca
Length of output: 2279
🏁 Script executed:
sed -n '110,180p' src/main/wsl/wsl-orcad-deploy.ts
rg -n -F -- 'probeManagedOrcadReadiness' src/main
rg -n -F -- 'type ServeReadiness' src/mainRepository: stablyai/orca
Length of output: 3562
🏁 Script executed:
sed -n '80,145p' src/main/ssh/orcad-managed-runtime-context.ts
sed -n '115,175p' src/main/ssh/orcad-runtime-deployment.ts
sed -n '120,165p' src/main/ssh/orcad-runtime-maintenance.tsRepository: stablyai/orca
Length of output: 6222
🏁 Script executed:
rg -n -F -- 'function probeActiveOrcadReadiness' src/main
rg -n -F -- 'probeActiveOrcadReadiness' src/main
rg -n -F -- 'wakeStoppedManagedOrcad' src/main/**/*.test.ts
sed -n '1,180p' src/main/ssh/orcad-managed-wake.tsRepository: stablyai/orca
Length of output: 14178
🏁 Script executed:
sed -n '1,150p' src/main/ssh/orcad-active-readiness.ts
sed -n '1,190p' src/main/wsl/wsl-orcad-deploy.test.ts
sed -n '90,145p' src/main/ssh/orcad-managed-serving.test.tsRepository: stablyai/orca
Length of output: 13725
🏁 Script executed:
rg -n -F -- 'function ensureManagedOrcadServing' src/main/ssh
sed -n '1,180p' src/main/ssh/orcad-managed-serving.ts
sed -n '180,380p' src/main/wsl/wsl-orcad-deploy.test.tsRepository: stablyai/orca
Length of output: 10764
Handle non-serving wake outcomes before probing readiness.
When deployed.outcome === 'already-active', wakeStoppedManagedOrcad(slot) can return fenced, unverifiable, or not-activated. The branch ignores these outcomes and calls probeManagedOrcadReadiness again. That probe can throw an OrcadActiveReadinessError based only on the second liveness check, so the caller can lose the wake outcome that explains why the slot was not started. Map each non-serving, non-started outcome to an explicit error before the readiness probe. Keep serving and started on the normal readiness path.
…returning detection Main (stablyai#27713) replaced wrapWindowsCmdHookCommand with wrapWindowsBatchHookCommand, which also sends UNC homes to the encoded launcher; Bob registers through it like ZCode, Kiro and Droid. Main's install-dir fallback tests (stablyai#27819) mock the fallback detection calls, which on this branch is resolveCommandsInInstallDirs (paths kept for the identity probe), so the mocks follow the rename; the PATH assertions are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stacked on #27800 (C3, the distro holder). Do not merge.
ELI5
Orca can already install and start its server (orcad) on an SSH machine. This change lets it do the same inside a WSL distro on Windows. It copies the server in, starts it so that it keeps running after the command that started it ends, and makes sure the server finds the user's Linux tools (claude, codex) rather than the Windows copies that WSL also puts on PATH. Nothing in the UI changes yet. Step C5 will connect the desktop to this server.
What Changed
deployAndLaunchWslOrcad(distro, options?)insrc/main/wsl/wsl-orcad-deploy.ts. It returns{ outcome: 'ready', port, fullVersion, pid, distroId, readiness, holder }, or{ outcome: 'deferred', code, reason, forceable }when live terminals would block an update. In order, it:OrcadHostUnsupportedError, the same refusal class SSH uses);ensureHeld) before anything is launched;deployOrcad(install and activation fence/journal), the pinned Node store, the same census and downgrade admission, readiness, and version GC;OrcadRemoteContext<Target = SshTarget>andresolveOrcadRemoteContextare now generic over the host target. Every SSH call site still getsSshTargetwith no edits. The SSH-only platform memo is skipped for WSL. The census and downgrade-admission policy moved intomanagedOrcadDeployPolicy(shared, same text), so SSH and WSL cannot drift.orcadLaunchCommand(host, spec, detach)takes a new'setsid'mode, picked by the transport's optionalorcadLaunchDetach(the WSL transport sets it; SSH leaves it unset, so its command is byte-for-byte unchanged). The forked child writes its own PID because thesetsid -fparent exits at once. busyboxsetsidhas no-f, so on busybox it falls back to a backgroundedsetsid. Readiness still comes from.orcad-readiness./mntcopy stays as an option (uploadSource: 'guest-mount').$SHELL -ilc(it reportsisPackaged), andgetPreflightWslTargetis null on Linux, so orcad inside the guest is never treated as a WSL preflight context. Both are now pinned by tests. New: inside a WSL guest, local agent detection skips the Windows-drive PATH entries (drvfs/9p/virtiofs mounts, the same rule as the existing guest lookup script). This covers the preflight walk, the install-dir fallback, and the agent-hook CLI presence check.readWslDistroRegistryId(distro)readsHKCU\…\Lxss\{guid}\DistributionNamewith a singlereg.exe querythrough process-host, the existing convention (no PowerShell). The native registry addon cannot list subkeys.adoptHoldersnow reports the distro name with its original casing (taken from the record), not the lowercase file name.Why
Reusing the SSH install and activation code keeps one fence, journal, and admission policy for every orcad host. The context is generic, with SSH as the default type argument, rather than a wider union. A union would have meant editing about 26 SSH call sites that read SSH-only fields. The detach mode is a transport capability, not a target check, so existing transport fakes and SSH connections need no changes. We measured that
nohup … &launched throughwsl.exe --execis killed when thatwsl.exeexits on a distro without systemd.setsid -fputs the server in its own session, and it survives.Linked Issue
Internal WSL-via-orcad tracker, step C4.
Visual Proof
N/A: no UI. This step has no user-visible surface; C7 adds the UI.
Testing
Real host (win-lowspec, Windows 11, WSL 2.7.10). I used a throwaway Ubuntu 24.04 rootfs imported as
orca-c4-probewithsystemd=falseand a non-root user. That user had a fake~/.local/bin/claude, and the Windows host had npm shims for claude, codex, and opencode on/mnt/c. I ran the bundleddeployAndLaunchWslOrcadwithOrca.exeas Node (ELECTRON_RUN_AS_NODE), using a template that carries this branch's orcad JS.ready,holder: spawned, GUID read.holder: adopted), after the launchingOrca.exehad exited and the distro had sat idle for more than 20 s. Its session id equals its PID, which confirmssetsidworked.wsl.exeround trips at roughly 180 ms each.SHELL=/bin/bash,WSL_DISTRO_NAMEset):/mnt/c/.../npm/*.claude → /home/u/.local/bin/claude, and codex and opencode are not detected.Automated. New tests cover target refusals (WSL 1, libc), holder-before-launch ordering, the deferral path, the wake of a dead active slot, the setsid launch (a nohup regression test, plus a Linux-only test that really detaches), the hydration gate,
/mntfiltering, the registry parse, and the holder casing.src/main/ssh,src/main/wsl,src/main/orcad,src/main/ipc, andsrc/main/agent-hookspass underconfig/vitest.config.ts. A few real-shell timing tests are flaky under full parallel load and pass when run alone.pnpm tc,check:code-quality:changed, and the static-analysis ratchets also pass. I ran the generated launch command inubuntu:20.04(util-linuxsetsid -f) andalpine:3.20(busybox fallback) containers.AI Disclosure
Review
Agent skill upstream boundary
Notes
reg.exethrough process-host. The PATH filter only applies inside a WSL guest (detected byWSL_DISTRO_NAME,WSL_INTEROP, or the interop binfmt).wsl.exeprobes dominate the wake path.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)