Skip to content

feat(wsl): accounts shim for agents on a WSL orcad host (WSL-via-orcad C9) - #27874

Merged
nwparker merged 2 commits into
mainfrom
nwparker/wsl-c9-accounts-shim
Oct 13, 2026
Merged

nwparker merged 2 commits into
mainfrom
nwparker/wsl-c9-accounts-shim

Conversation

@nwparker

@nwparker nwparker commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​426 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​426
Prod 21 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​414 $\color{#cf222e}{\Huge{\mathbf{−}}}$​42 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​372

Stacked on #27916 (C6, liveness for the WSL orcad connection), which sits on #27834 (C5, connect to a WSL distro's orcad). Do not merge.

ELI5

When Orca runs agents inside a WSL distro through that distro's own Orca server (orcad), the server has no idea which Claude or Codex account you picked, because account management lives only in the desktop app. This change makes the desktop do the account work, as it already does for local WSL terminals, and hand the server the answer: "start this agent with this account folder". The server just passes it to the agent's process.

What Changed

  • Desktop shim (src/main/wsl/wsl-orcad-account-env.ts). Before a renderer call reaches a WSL orcad host, the one runtimeEnvironments:call handler asks the existing WSL account code for the selected account's in-distro home:
    • Claude: ClaudeRuntimeAuthService.prepareForClaudeLaunch({runtime:'wsl', wslDistro}), the same ClaudeWslProfileRouter path local WSL panes use (setup, guest pointer file). A Claude launch gets CLAUDE_CONFIG_DIR + ORCA_CLAUDE_INJECTED_CONFIG_DIR + ORCA_CLAUDE_PROFILE_POINTER; any other process gets the pointer only, so a typed claude still follows the account.
    • Codex: the Codex runtime home's WSL selection (prepareForCodexLaunchAsync), converted from its UNC path to the guest path, sent as CODEX_HOME only when an account is selected. ORCA_CODEX_HOME is deliberately not sent: orcad strips CODEX_HOME when it equals that marker. The desktop's Codex hook install is skipped, because agent status on a WSL host belongs to that host's orcad.
    • The env rides terminal.create / session.tabs.createTerminal env only where adding env cannot change the call's meaning: a plain shell, or a create that already brings its own launch (env, launchConfig or launchAgent). A bare command or an agent preset is left alone, since env would switch off the host's Settings-default launch.
    • agent.launch / agent.launchReplay get the new optional accountEnv (below). A reused terminal is skipped.
    • If the account cannot be prepared (missing folder, failed setup), the call is refused with account_preparation_failed rather than started under another account, the same rule local launches follow.
  • Which environments are WSL hosts (wsl-orcad-host-environments.ts). WslOrcadConnectionManager registers environmentId → distro while a connection is open and removes it on close. C7 must pair the runtime environment under connection.environmentId.
  • agent.launch accountEnv (host side). New optional field, validated by AgentLaunchAccountEnv: only the four routing variables, only absolute POSIX paths (the pointer may be ~/), no .., no control characters, and the injected marker must equal CLAUDE_CONFIG_DIR. A bad value refuses the launch rather than dropping it. It flows intent → executor → terminal surface (TerminalCreateOptions.accountEnv, merged over the startup plan's env) and → a created worktree's startup terminal (startupAccountEnv). It is outside the replay fingerprint.
  • Structured sessions (decision 3). Not given a client-chosen home. Their home stays host-derived, and a structured launch that carried accountEnv returns the warning "Account selection is not supported for this session type on WSL hosts yet; the chat uses the host default account." A validated client hint needs a host-side check of the path against the distro's account folders, which belongs with the orcad account-services port (D5).
  • /mnt/<drive> at launch time. resolveStructuredAgentCommand (the structured host's Claude/Codex/ACP/Pi binary resolution) now resolves through guestNativePathEnv, which drops PATH dirs on Windows drive mounts inside a WSL guest, the same rule C4's detection uses. It is a no-op everywhere else.

Why

D10: orcad's account services depend on Electron (paths, network, secret store), so porting them is D5. The desktop already has working, tested WSL account code that talks to the distro over wsl.exe and does not care how the agent is spawned. Reusing it and passing plain env keeps orcad unchanged; orcad already passes a client's env through, since it never installs account services.

Hooking the single main-process handler, rather than threading account env through four renderer create paths, keeps the renderer and the wire unchanged for every non-WSL host. The alternative of setting the pointer in orcad's own process env would route Claude for every caller but cannot route Codex per launch.

Wire compatibility

  • agent.launch.accountEnv is a new optional field on a non-strict schema. An older host strips it and starts its default account, as before this change; a test parses a new payload with the pre-field schema. An older client never sends it, and a new host parses that payload identically (tested). It is excluded from the replay fingerprint, so a retry with a different account still replays (tested).
  • terminal.create / session.tabs.createTerminal env already exist on every supported host. No new opcode, capability or published content.
  • terminal.createAgentSession / ensureAgentSession are .strict(), so adding a field there would break older hosts. They are not patched; on a WSL host those launches run the distro's default account until a capability-gated field is added (follow-up).
  • CLI and mobile are unaffected: the shim lives in the desktop's main process, and they talk to hosts directly.

Linked Issue

Internal WSL-via-orcad tracker, step C9 (R13; decision 3).

Visual Proof

N/A: no UI. C7 adds the WSL host 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-c9-probe, with systemd=false placed in the rootfs before its first boot (PID 1 was init), and a non-root user u. A harness bundled from this branch ran under the installed Orca.exe as Node: WslOrcadConnectionManager.ensure (C4/C5, daily orcad template, 8–30 s), then withWslOrcadAccountEnv with stand-in account services that answer like the WSL Claude router and the Codex runtime home (Claude account A at /home/u/.c9-accounts/claude-a/config, Codex account B as the UNC path \\wsl.localhost\orca-c9-probe\home\u\.c9-accounts\codex-b), then terminal.create over the bridge. Each pane echoed its env:

Call Sent env Pane saw
Plain shell pointer + CODEX_HOME A=none P=~/.c9-accounts/selected-claude B=/home/u/.c9-accounts/codex-b
launchAgent: claude + caller env Claude patch + CODEX_HOME, caller key kept A=/home/u/.c9-accounts/claude-a/config I=(same) P=… B=/home/u/.c9-accounts/codex-b
launchAgent: codex + caller env pointer + CODEX_HOME A=none P=… B=/home/u/.c9-accounts/codex-b
Bare command none (left untouched) A=none P=none B=none

So the daily orcad passes a client's CLAUDE_CONFIG_DIR and CODEX_HOME through untouched, and the UNC → guest conversion holds. ORCA_CODEX_HOME was never set in a pane. The host-side agent.launch and /mnt changes are not in that orcad build, so they are covered by unit tests only. Cleanup: the holder was killed, the distro terminated and unregistered, and the temp dir and both terminals removed; wsl -l -v matches the starting state and no orca-c9- processes remain.

Automated.

  • wsl-orcad-account-env.test.ts: env per account (Claude launch, other process, System default, a host-routed or other-distro answer dropped, an unusable path refused), UNC → guest path, which methods are patched or left alone, refusal on a failed setup.
  • agent-launch-params-account-env.test.ts: new/old host and new/old client parsing, fingerprint, and seven refused shapes.
  • agent-launch-account-env.test.ts: the executor passes accountEnv to a terminal agent and a created worktree's startup terminal, never to a structured session, and warns there.
  • runtime-worktree-agent-startup.test.ts: the startup env merge.
  • structured-agent-command-guest-path.test.ts: a drive-mount shim is skipped for the Linux CLI (explicit PATH, process PATH, Command-setting PATH), and nothing changes off a guest.
  • wsl-orcad-connection.test.ts: the environment is listed only while connected.
  • claude-profile-wsl-router.test.ts: usage polling (accountUsagePreparation, preparation) reads the same guest folder the launch patch names. Codex polling uses the same getWslCodexHomePathForSelection as launch (by reading).
  • Suites: agent-launch, runtime rpc methods, claude-accounts, codex-accounts, wsl, native-chat, rpc-contract, ipc, pty, orca-runtime-tests: 12849 passed, 1 failed (preflight-agent-detection "does not double-count…", which fails on the C5 base too, from C4's install-dir code).
  • pnpm tc, oxlint, check:code-quality:changed, reliability-gates, dead-classes, max-lines / ts-nocheck / runtime-electron / focus-read / layout-writer ratchets, node-runtime-pin, verify:rpc-params-catalog, process-host and compat-boundary imports, pnpm format: all pass.

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: unaffected; only environments the WSL connection manager registered are patched.
  • Security: the host accepts only four variable names with absolute guest paths, and only for terminal agents, which could already be given any env through terminal.create. Structured sessions never read it.
  • Open: createAgentSession / ensureAgentSession (strict) need a capability-gated field; resumed sleeping agents re-launch without the account env (not persisted in launchConfig); structured sessions stay on the host default until D5.

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-c5-bridge-pairing branch from 67bff99 to 5e76a1b Compare October 12, 2026 10:39
@nwparker
nwparker force-pushed the nwparker/wsl-c9-accounts-shim branch 2 times, most recently from 03a2bce to 3f9b83d Compare October 12, 2026 10:45
@nwparker
nwparker changed the base branch from nwparker/wsl-c5-bridge-pairing to nwparker/wsl-c6-liveness October 12, 2026 10:46
@nwparker
nwparker force-pushed the nwparker/wsl-c6-liveness branch from 2b419a3 to caf2132 Compare October 12, 2026 12:00
@nwparker
nwparker force-pushed the nwparker/wsl-c9-accounts-shim branch 2 times, most recently from 5817b05 to 3f949b6 Compare October 12, 2026 16:25
@nwparker
nwparker force-pushed the nwparker/wsl-c6-liveness branch 2 times, most recently from 7d5d2c4 to 4e4aa55 Compare October 13, 2026 13:14
…d C9)

The desktop prepares the selected Claude/Codex account's in-distro home with the
existing WSL account code and sends it as env on terminal.create /
session.tabs.createTerminal, or as agent.launch's new optional accountEnv.
Structured sessions keep their host-derived home and warn. Launch-time CLI
resolution inside a WSL guest skips /mnt/<drive> shims.
@nwparker
nwparker changed the base branch from nwparker/wsl-c6-liveness to main October 13, 2026 14:09
@nwparker
nwparker marked this pull request as ready for review October 13, 2026 14:09
@nwparker
nwparker force-pushed the nwparker/wsl-c9-accounts-shim branch from 3f949b6 to 93113ed Compare October 13, 2026 14:09
@nwparker

Copy link
Copy Markdown
Contributor Author

Merge-train review (blast radius outside WSL): LOW. accountEnv on agent.launch is optional and additive. Older hosts strip it. It accepts only four account-routing variables holding absolute POSIX paths, which is a strict subset of what terminal.create's env already accepts. Only the desktop's WSL shim sets it, and that shim acts only for environments registered by an open WSL orcad connection. Guest-PATH filtering for launch resolution inherits the C4 scoping, so it applies only to an orcad that Orca launched as a WSL host. Startup wiring installs callbacks only.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

The launch contract now accepts validated account environment values. WSL Orcad connections register their distro, and eligible runtime calls receive prepared Claude and Codex account values. Terminal-agent and worktree startup environments merge account values over existing values; structured launches can report ignored inputs. Structured-agent command resolution also filters Windows-drive mount directories from guest PATH values.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 93113

A reconnect can temporarily lose selected-account routing, causing launches to use the host default account. Fix the close ordering before merging, or explicitly accept this bounded risk.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 35.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 28 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
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.
Title check Passed The title clearly identifies the WSL orcad account shim, which is the main change. It is specific and understandable, despite including a workstream reference.
Description check Passed The description covers the change, rationale, compatibility, testing, visual-proof status, and known limitations. It reports the failing test and leaves the general lint, typecheck, test, and build ch…
  • 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: 61ec8437-a441-4494-a22a-4f07443d23b4
📥 Commits

Reviewing files that changed from the base of the PR and between fc7dff8 and 93113ed.

📒 Files selected for processing (28)
  • src/main/agent-launch/agent-launch-account-env.test.ts
  • src/main/agent-launch/agent-launch-executor.ts
  • src/main/agent-launch/agent-launch-surface-factories.ts
  • src/main/agent-launch/agent-launch-warnings.ts
  • src/main/claude-accounts/claude-profile-wsl-router.test.ts
  • src/main/ipc/runtime-environment-connectivity-handlers.ts
  • src/main/ipc/wsl-guest-windows-drive-path.ts
  • src/main/native-chat/structured-agent-command-guest-path.test.ts
  • src/main/native-chat/structured-agent-command-resolution.ts
  • src/main/runtime/rpc/methods/agent-launch-intent-resolution.ts
  • src/main/runtime/rpc/methods/agent-launch-surfaces.ts
  • src/main/runtime/rpc/methods/agent-launch-worktree-creation.ts
  • src/main/runtime/runtime-agent-terminal-startup.ts
  • src/main/runtime/runtime-managed-worktree-create-types.ts
  • src/main/runtime/runtime-terminal-contracts.ts
  • src/main/runtime/runtime-worktree-agent-startup.test.ts
  • src/main/runtime/runtime-worktree-agent-startup.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/main/startup/wsl-orcad-account-env-wiring.ts
  • src/main/wsl/wsl-orcad-account-env.test.ts
  • src/main/wsl/wsl-orcad-account-env.ts
  • src/main/wsl/wsl-orcad-connection.test.ts
  • src/main/wsl/wsl-orcad-connection.ts
  • src/main/wsl/wsl-orcad-host-environments.ts
  • src/shared/agent-launch-account-env.ts
  • src/shared/agent-launch-intent.ts
  • src/shared/rpc-contract/agent-launch-params-account-env.test.ts
  • src/shared/rpc-contract/agent-launch-params.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.

Comment on lines +22 to +34
type WslOrcadHostEnvironmentRef = { environmentId: string; distro: string }

/** Lists `next` while open; a closed connection (`next` null) unlists `previous`. */
export function trackWslOrcadHostEnvironment(
previous: WslOrcadHostEnvironmentRef,
next: WslOrcadHostEnvironmentRef | null
): void {
if (next) {
registerWslOrcadHostEnvironment(next.environmentId, next.distro)
} else {
unregisterWslOrcadHostEnvironment(previous.environmentId, previous.distro)
}
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,80p' src/main/wsl/wsl-orcad-host-environments.ts
sed -n '100,320p' src/main/wsl/wsl-orcad-connection.ts

Repository: stablyai/orca

Length of output: 9606


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- remaining connection manager implementation ---'
sed -n '320,520p' src/main/wsl/wsl-orcad-connection.ts
printf '%s\n' '--- lifecycle and tracking usages ---'
rg -n -F --glob '*.ts' -e 'trackWslOrcadHostEnvironment' -e 'closeEntry(' -e 'new WslOrcadConnectionManager' -e '.close()' src/main/wsl src/main | head -240
printf '%s\n' '--- environment ID and account-routing bindings ---'
rg -n -F --glob '*.ts' -e 'defaultWslOrcadEnvironmentId' -e 'wslOrcadHostDistro' -e 'accountEnv' -e 'environmentId' src/main/wsl src/main | head -320

Repository: stablyai/orca

Length of output: 41777


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- exact manager lifecycle callers ---'
rg -n -F --glob '*.ts' -e 'WslOrcadConnectionManager' -e 'connectionManager.close' -e 'connections.close' -e 'wslOrcadConnections.close' -e '.ensure(' src/main/wsl src/main | head -220
printf '%s\n' '--- account environment implementation and consumers ---'
sed -n '1,190p' src/main/wsl/wsl-orcad-account-env.ts
rg -n -F --glob '*.ts' -e 'resolveWslOrcadAccountEnv' -e 'wslOrcadAccountEnv' -e 'wslOrcadHostDistro' -e 'accountEnvFor' src/main | head -180
printf '%s\n' '--- connection tests around close/replacement ---'
sed -n '1,130p' src/main/wsl/wsl-orcad-connection.test.ts
sed -n '250,330p' src/main/wsl/wsl-orcad-connection.test.ts

Repository: stablyai/orca

Length of output: 31782


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- liveness close and ensure paths ---'
rg -n -F --glob '*.ts' -e 'manager.close(' -e '.manager.close(' -e 'close: (distro' -e 'ensure: (options' src/main/wsl
sed -n '1,210p' src/main/wsl/wsl-orcad-liveness-supervisor.ts
sed -n '300,345p' src/main/wsl/wsl-orcad-liveness-supervisor.ts
sed -n '1,115p' src/main/wsl/wsl-orcad-liveness-recovery.ts
sed -n '1,120p' src/main/wsl/wsl-orcad-liveness-model.ts

Repository: stablyai/orca

Length of output: 17863


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- account-env integration ---'
rg -n -F --glob '*.ts' -e 'withWslOrcadAccountEnv' -e 'installWslOrcadAccountEnvDependencies' src/main src/shared
printf '%s\n' '--- exact changed boundary with line numbers ---'
nl -ba src/main/wsl/wsl-orcad-host-environments.ts | sed -n '1,45p'
nl -ba src/main/wsl/wsl-orcad-connection.ts | sed -n '150,180p;210,235p;255,275p;315,330p'
printf '%s\n' '--- exact supervisor lifecycle lines ---'
nl -ba src/main/wsl/wsl-orcad-liveness-supervisor.ts | sed -n '55,100p;315,335p'

Repository: stablyai/orca

Length of output: 9236


Unregister the WSL environment before awaiting bridge shutdown.

WslOrcadConnectionManager.close removes the active entry, then awaits forward.close(). A concurrent reconnect can register the same environment ID and distro during that wait. The old close then calls trackWslOrcadHostEnvironment(old, null), and the distro-only check deletes the new mapping. Later account-routed requests pass through without the WSL account environment.

Suggested fix
     this.active.delete(key)
-    await entry.forward.close()
     trackWslOrcadHostEnvironment(entry.connection, null)
+    await entry.forward.close()
     this.dependencies.onChange?.(entry.connection.distro, null, { state: 'closed' })

@nwparker
nwparker merged commit 41e6ea5 into main Oct 13, 2026
43 checks passed
registered2nd pushed a commit to registered2nd/orca that referenced this pull request Oct 14, 2026
…ht back (stablyai#28402)

* fix(ssh): a cancelled sign-in stays cancelled until the user reconnects

Co-Authored-By: Claude <noreply@anthropic.com>

* test(ssh): drive the credential cancel through the IPC harness

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): an explicit server connect lifts the cancelled sign-in of its SSH host

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): the cancelled attempt may still offer a password; a successful sign-in lifts the decline

Co-Authored-By: Claude <noreply@anthropic.com>

* test(ssh): build the managed-server link without the newer kind field

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit ed18c2c)

Release pick note: runtime-environment-connectivity-handlers.ts conflicted with main's WSL host series (stablyai#27874/stablyai#27940/stablyai#27933), which is not on this release. Kept only this PR's two lines there (the clearSshCredentialDeclinedForEnvironment import and call).
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