Skip to content

feat(wsl): connect to a WSL distro's orcad over wsl.exe stdio only (WSL-via-orcad C5) - #27834

Merged
nwparker merged 1 commit into
mainfrom
nwparker/wsl-c5-bridge-pairing
Oct 13, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/wsl-c5-bridge-pairing

Conversation

@nwparker

@nwparker nwparker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​397 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​397
Prod 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​438 $\color{#cf222e}{\Huge{\mathbf{−}}}$​5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​433

Stacked on #27941 (C4c, wake-path perf), which sits on #27923 and #27819 (C4, deploy and launch orcad in WSL). Do not merge.

ELI5

C4 can start Orca's server (orcad) inside a WSL distro. This change lets the desktop actually talk to that server. Every connection is carried through its own wsl.exe process, just like a pipe. The desktop never opens a network connection into WSL, because WSL's localhost forwarding is unreliable for the first second after a server starts listening, and it behaves differently in each WSL networking mode. Nothing in the UI changes yet. C7 wires this up and saves it.

What Changed

  • Bridge (src/main/wsl/wsl-orcad-stdio-bridge.ts): listenWslOrcadStdioBridge(transport, { environmentId, remotePort, localPort, label }) starts the merged listenOrcadStdioBridge listener on 127.0.0.1.
    • Each accepted socket gets its own exec of the existing POSIX bridge script, through the C1 wsl.exe --exec sh -c transport.
    • The script runs on the pinned Node that C4 installed in ~/.orca-remote/runtimes, and the existing READY/REFUSED/NO_NODE sentinel decides what happens to the socket.
    • The cap of 8 and the queue are the listener's own, unchanged.
  • Connection (src/main/wsl/wsl-orcad-connection.ts): WslOrcadConnectionManager.ensure(distro, opts) returns WslOrcadConnection { distro, environmentId, localPort, remotePort, fullVersion, pairingOffer, close() }. It:
    1. calls deployAndLaunchWslOrcad, which installs, wakes, or adopts orcad (a deferral throws WslOrcadLaunchDeferredError);
    2. starts the bridge at the port orcad says it bound;
    3. rewrites orcad's pairing offer to ws://127.0.0.1:<localPort> with the same tunneledOrcadPairingCode that SSH uses;
    4. proves the server's identity with the same pairing handshake as SSH (verifyOrcadPairingIdentity). An answer from another runtime is refused.
  • Re-entrancy:
    • There is one connection per distro, and the distro name is matched without regard to case.
    • Concurrent ensure calls share one run.
    • If the port is the same, the bridge is kept. If the offer changed, the new one is reported.
    • If orcad's port moved after a relaunch, the bridge is rebuilt on the same local port, so a saved pairing code keeps working.
    • A close() that arrives while a connect is still running cancels that connect.
    • There are also get, list, close, dispose, and an onChange(distro, connection | null) callback for C7 to save to disk.
  • Small edits outside these files:
    • startOrcadStdioBridge and its dependency type are now exported from the SSH provider file. SSH behavior is byte-identical.
    • defaultWslOrcadEnvironmentId(distro) is pulled out of C4's deploy function.

Why

The merged listener, bridge script, sentinel, pairing rewrite and identity check already work with any transport, so WSL reuses them as-is. The managed-tunnel manager does not: it depends on SSH targets, the SSH connection manager, and SSH resume recovery. Widening it would have meant changing SSH code paths, so a small WSL manager owns the reconnect policy instead.

Real-host finding: every WSL2 distro shares one network namespace. Another distro's orcad already held 6768 and 6769, so this one bound 6770. The bridge followed the bound port, and the identity check stops a bridge from pairing with another distro's orcad.

Linked Issue

Internal WSL-via-orcad tracker, step C5.

Visual Proof

N/A: no UI. C7 adds the surface.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Real host (win-lowspec, Windows 11, WSL 2.7.10). I used a throwaway Ubuntu 24.04 distro, orca-c5-probe, with systemd=false placed in the rootfs before its first boot (PID 1 was init), and a non-root user. I ran the bundled branch code with Orca.exe as Node and the daily-build orcad template. Then I pointed the installed Windows orca.exe CLI at the rewritten code.

  • Cold connect: 25.5 s, including the install. Warm connects (holder adopted) took 8.9–9.1 s.
  • CLI over the bridge:
    • orca status --pairing-code: reachable.
    • orca repo add --path /home/u/repo: ok.
    • orca terminal create --worktree path:/home/u/repo --command 'echo VIA_C5' followed by terminal read --screen shows the u@win:~/repo$ prompt and the VIA_C5 output line.
  • No TCP into WSL: Get-NetTCPConnection for the harness showed only its 127.0.0.1 listener.
  • Concurrency:
    • 12 parallel orca status calls: 12/12 ok in 2.7 s.
    • 12 held raw sockets: 9 direct wsl.exe children (8 bridges plus the holder), so 4 were queued. Releasing 4 sockets admitted the queued ones, still 8 bridges. With everything closed, only the holder was left.
    • Each bridge shows up as two wsl.exe processes (a launcher and its child): 6 held sockets meant 12 bridge wsl.exe processes and 6 bridge node processes in the guest.
  • Port move: I killed orcad, blocked 6770 inside the distro, and called ensure again (14 s). orcad came back on 6771. The local port stayed the same. The old and new pairing codes both returned status ok, and the terminal survived (terminal list = 1).
  • Teardown with 6 live sockets: zero bridge wsl.exe processes on Windows and zero bridge node processes in the guest. The only orca-c5-probe processes left were the 2 detached-holder ones.
  • Cleanup: I killed the holder, unregistered the distro, and removed the temp dir and the terminal. wsl -l -v matches the starting state.

Automated.

  • wsl-orcad-stdio-bridge.test.ts: the real listener and the real WSL transport, with only the wsl.exe spawn faked. Covers one child per socket, the cap of 8 with queueing and slot reuse, a refused port, and teardown.
  • wsl-orcad-connection.test.ts: the pairing rewrite, a shared concurrent connect, bridge reuse on the same port, a rotated offer, a port move to the same local port, a deferral, a foreign identity, a close racing a connect, and teardown.
  • pnpm tc, oxlint, check:code-quality:changed, the static-analysis ratchets, and pnpm format all pass.
  • src/main/{ssh,wsl,orcad,ipc}: everything passes except preflight-agent-detection ("does not double-count … install-dir resolver"). That test also fails when run alone, and it covers C4's install-dir code, which this PR does not touch. The other failures were timing flakes under full parallel load and pass when run alone.

AI Disclosure

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.

Notes

  • SSH: no behavior change. The only edit is an export on the existing transport-agnostic bridge starter.
  • Wire: no protocol, RPC or published-content change. The rewritten offer has the same shape SSH tunnels produce.
  • Windows: every bridge is a wsl.exe --exec child through process-host (buildWslExecArgs), never a shell, and never a TCP connection into the VM.
  • Open:
    • A relaunched orcad publishes a new device token. The old one is still accepted, but C7 should save what onChange reports.
    • There is no liveness watcher yet; C6 owns bridge EOF and distro-stop detection.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@nwparker
nwparker force-pushed the nwparker/wsl-c4-deploy-launch branch from ca22e1a to 21a0166 Compare October 12, 2026 10:38
@nwparker
nwparker force-pushed the nwparker/wsl-c5-bridge-pairing branch from 67bff99 to 5e76a1b Compare October 12, 2026 10:39
@nwparker
nwparker changed the base branch from nwparker/wsl-c4-deploy-launch to nwparker/wsl-c4c-wake-path-perf October 12, 2026 10:40
@nwparker
nwparker force-pushed the nwparker/wsl-c4c-wake-path-perf branch from c745dd1 to a0524da Compare October 12, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-c5-bridge-pairing branch from 5e76a1b to 3fb1697 Compare October 12, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-c4c-wake-path-perf branch from a0524da to cd98e85 Compare October 12, 2026 16:25
@nwparker
nwparker force-pushed the nwparker/wsl-c5-bridge-pairing branch from 3fb1697 to 4e55a67 Compare October 12, 2026 16:25
@nwparker
nwparker force-pushed the nwparker/wsl-c4c-wake-path-perf branch 2 times, most recently from 5881524 to c6338e9 Compare October 13, 2026 12:00
@nwparker
nwparker changed the base branch from nwparker/wsl-c4c-wake-path-perf to main October 13, 2026 12:43
@nwparker
nwparker marked this pull request as ready for review October 13, 2026 12:43
@nwparker
nwparker force-pushed the nwparker/wsl-c5-bridge-pairing branch from 4e55a67 to 6837a61 Compare October 13, 2026 12:43
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

The change exports the shared stdio bridge dependencies and startup function, then adds a WSL bridge wrapper. It adds a per-distro connection manager that deploys Orcad, verifies pairing identity, manages bridge reuse and replacement, and reports connection changes. New tests cover bridge socket behavior and connection lifecycle cases.

Estimated code review effort

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 6837a

A same-port update could expose a connection to an unverified server. Verify the replacement’s identity before merging.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly identifies the main change: WSL stdio-only connectivity to an Orcad server in a WSL distro.
Description check Passed The description is detailed and covers the required ELI5, changes, rationale, testing, visual proof, notes, and checklist sections. It is mostly complete, but it does not provide an actual linked issu…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e0cec902-3727-4e42-8d37-8f67104c5636
📥 Commits

Reviewing files that changed from the base of the PR and between 0d9ceca and 6837a61.

📒 Files selected for processing (7)
  • src/main/ssh/orcad-stdio-bridge-provider.ts
  • src/main/wsl/wsl-orcad-bridge-test-child.ts
  • src/main/wsl/wsl-orcad-connection.test.ts
  • src/main/wsl/wsl-orcad-connection.ts
  • src/main/wsl/wsl-orcad-deploy.ts
  • src/main/wsl/wsl-orcad-stdio-bridge.test.ts
  • src/main/wsl/wsl-orcad-stdio-bridge.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +160 to +180
// Every call relaunches a stopped orcad, so a distro restart is recovered here.
const launch = await (this.dependencies.deployAndLaunch ?? deployAndLaunchWslOrcad)(distro, {
environmentId,
label,
force: options.force,
signal: options.signal,
holder: this.dependencies.holder,
createTransport: () => transport
})
if (launch.outcome === 'deferred') {
throw new WslOrcadLaunchDeferredError(launch)
}
if (this.generation(key) !== generation) {
throw new WslOrcadConnectionSupersededError(distro)
}
const current = this.active.get(key)
if (
current &&
current.connection.remotePort === launch.port &&
current.connection.environmentId === environmentId
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '145,310p' src/main/wsl/wsl-orcad-deploy.ts
rg -n 'runtimeId|readiness|pairingOffer|already.running|alreadyRunning' src/main/wsl/wsl-orcad-deploy.ts src/main/wsl/wsl-orcad-connection.ts src/main/wsl/wsl-orcad-connection.test.ts

Repository: stablyai/orca

Length of output: 4863


🏁 Script executed:

set -e
printf '%s\n' '--- connection manager ---'
sed -n '1,330p' src/main/wsl/wsl-orcad-connection.ts
printf '%s\n' '--- deploy definitions and imports ---'
sed -n '1,230p' src/main/wsl/wsl-orcad-deploy.ts
printf '%s\n' '--- bound symbols ---'
rg -n -F --glob '*.ts' -e 'deployAndLaunchWslOrcad' -e 'verifyWslOrcadIdentity' -e 'probeManagedOrcadReadiness' -e 'adopt' -e 'already-active' -e 'isManagedOrcadReady' src/main src | head -240
printf '%s\n' '--- identity and readiness definitions ---'
sed -n '1,220p' src/main/server/serve-readiness.ts
rg -n -F --glob '*.ts' -e 'function verifyWslOrcadIdentity' -e 'const verifyWslOrcadIdentity' -e 'verifyWslOrcadIdentity =' -e 'parsePairingCode' -e 'tunneledOrcadPairingCode' src/main src
printf '%s\n' '--- connection tests ---'
sed -n '1,280p' src/main/wsl/wsl-orcad-connection.test.ts
printf '%s\n' '--- deploy tests ---'
rg -n -F --glob '*.test.ts' -e 'deployAndLaunchWslOrcad' -e 'already-running' -e 'adoptedInstance' -e 'rotated-token' -e 'runtimeId' src/main/wsl

Repository: stablyai/orca

Length of output: 42920


🏁 Script executed:

set -e
printf '%s\n' '--- identity implementation ---'
sed -n '1,240p' src/main/ssh/orcad-managed-tunnel-identity.ts
printf '%s\n' '--- identity restart tests ---'
sed -n '1,260p' src/main/ssh/orcad-managed-tunnel-identity-restart.test.ts
printf '%s\n' '--- readiness probe implementation ---'
rg -n -F -- 'probeManagedOrcadReadiness' src/main/ssh
sed -n '1,360p' src/main/ssh/orcad-managed-runtime-context.ts
printf '%s\n' '--- deploy implementation and active-instance behavior ---'
rg -n -F -- 'export async function deployOrcad' src/main/ssh
rg -n -F -- 'outcome: '\''already-active'\''' src/main/ssh
sed -n '1,360p' src/main/ssh/orcad-remote-deploy.ts
printf '%s\n' '--- related deploy/adoption test blocks ---'
sed -n '80,270p' src/main/wsl/wsl-orcad-deploy.test.ts
sed -n '1,220p' src/main/wsl/wsl-orcad-instance-adoption.test.ts

Repository: stablyai/orca

Length of output: 37753


🏁 Script executed:

set -e
printf '%s\n' '--- activation result implementation ---'
sed -n '1,360p' src/main/ssh/orcad-installed-activation.ts
printf '%s\n' '--- wake implementation ---'
sed -n '1,300p' src/main/ssh/orcad-managed-wake.ts
printf '%s\n' '--- active readiness implementation ---'
sed -n '1,340p' src/main/ssh/orcad-active-readiness.ts
printf '%s\n' '--- relevant callers and tests ---'
rg -n -F --glob '*.ts' -e 'wakeStoppedManagedOrcad' -e 'probeActiveOrcadReadiness' -e 'outcome: '\''already-active'\''' -e 'serving' src/main/ssh src/main/wsl

Repository: stablyai/orca

Length of output: 42387


🏁 Script executed:

set -e
printf '%s\n' '--- update-plan action conditions ---'
rg -n -F -- 'export function planOrcadUpdate' src/main/ssh
sed -n '1,300p' src/main/ssh/orcad-update-plan.ts
printf '%s\n' '--- complete active readiness probe ---'
sed -n '75,230p' src/main/ssh/orcad-active-readiness.ts
printf '%s\n' '--- launch and port behavior ---'
rg -n -F -- 'launchAndJudgeOrcadSlot' src/main/ssh/orcad-candidate-launch-verdict.ts src/main/ssh
sed -n '1,260p' src/main/ssh/orcad-candidate-launch-verdict.ts

Repository: stablyai/orca

Length of output: 17872


Verify the refreshed offer before reusing the bridge.

When an update changes the active version but keeps the same remote port, activation stops the incumbent and starts the candidate. The same-port branch then refreshes the offer without performing the pairing handshake. The previous bridge verification does not prove the identity of the replacement process.

Suggested fix
   private refreshOffer(
     key: string,
     entry: ActiveWslOrcadConnection,
     launch: Extract<WslOrcadLaunch, { outcome: 'ready' }>
-  ): WslOrcadConnection {
+  ): Promise<WslOrcadConnection> {
     const pairingOffer = tunneledOrcadPairingCode(launch.readiness, entry.connection.localPort)
+    const identity = await (this.dependencies.verifyIdentity ?? verifyWslOrcadIdentity)(
+      pairingOffer,
+      launch.readiness
+    )
+    if (identity.verdict === 'foreign') {
+      this.active.delete(key)
+      await entry.forward.close()
+      this.dependencies.onChange?.(entry.connection.distro, null)
+      throw new OrcadManagedIdentityError(launch.port, identity.detail)
+    }
+    if (identity.verdict === 'unreachable') {
+      console.warn(
+        `[wsl] Orca server in ${entry.connection.distro} did not answer its bridge: ${identity.detail}`
+      )
+    }
     if (
       pairingOffer === entry.connection.pairingOffer &&
       launch.fullVersion === entry.connection.fullVersion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant