Skip to content

Fix CLI delivery and fresh launch environments for managed SSH agents - #26464

Merged
brennanb2025 merged 30 commits into
mainfrom
brennanb2025/ssh-chat-remote-launch-env
Oct 14, 2026
Merged

brennanb2025 merged 30 commits into
mainfrom
brennanb2025/ssh-chat-remote-launch-env

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 24 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1651 $\color{#cf222e}{\Huge{\mathbf{−}}}$​29 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1622
Prod 24 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​540 $\color{#cf222e}{\Huge{\mathbf{−}}}$​172 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​368

The problem

When you open a chat on a remote machine over SSH, Orca installs its own small server on that machine, called the "managed server". The agent, such as Claude, runs there, not on your laptop. Three things went wrong for agents started this way.

1. An agent's orca command could talk to the wrong Orca

Agents use the orca command to reach Orca itself, for example to message their coordinator or check their mail. Main installs that command on managed servers (#26539 for Linux and macOS, #27324 for Windows).

Before orca does anything, it decides which Orca to talk to, in this order:

  1. a target you name on purpose (--environment or --pairing-code);
  2. a target named in environment variables (ORCA_ENVIRONMENT, ORCA_PAIRING_CODE or ORCA_REMOTE_PAIRING);
  3. the Orca server on this machine.

Before: Orca reads the remote machine's shell setup (for example ~/.profile) and passes it to agents. If that setup exported ORCA_ENVIRONMENT or a pairing code, perhaps left over from some other use, an agent's orca calls went to whatever Orca that named, not to the server running the agent. Its messages to its coordinator failed.

After: agents always reach the server that launched them, and your own settings work normally.

  • When the server starts an agent, it hands the agent its own connection defaults through the variables orca already reads: the server's data folder, and the three target variables set to empty, which orca treats as "not set". A leftover setting in the shell setup can no longer redirect the agent.
  • A target named on purpose (--environment, --pairing-code) still works for agents.
  • A person typing orca in a terminal on that server is not affected: if your shell exports ORCA_ENVIRONMENT, orca follows it, exactly as on main.

2. A custom Claude command on the remote machine was refused

Before: your shell setup on the remote machine usually adds folders to the list of places the system searches for programs (PATH). Codex and the other agents looked up their program after reading that setup; Claude looked up its program before. So a Claude that only your shell setup could find was reported as not runnable.

After: Claude looks up its program the same way, and at the same point, as the other agents.

3. Installing an agent on the remote machine was ignored until Orca restarted

Before: the server read your shell setup once, when it started, and kept that copy forever. Installing an agent, fixing its path, or adding a folder to PATH did nothing for new chats until the server restarted.

After:

  • The server keeps a saved copy of your shell setup and refreshes it in the background when it is more than 10 seconds old. Chats start right away with the saved copy; only the very first chat after the server starts can wait for the first read, as on main.
  • The first chat right after an install starts. If an agent's program isn't found with the saved copy, the start waits for a reread that began after it looked (at most 5 seconds, one shell's time limit) and looks again before saying the agent wasn't found. A reread that was already running from before the install doesn't count. A start that finds its program never waits, including one found through a relative PATH entry such as node_modules/.bin, which is checked from the chat's folder; a relative entry that doesn't hold the program still counts as not found.
  • The check OpenCode, OMP and Pi run before a chat is created reads the same saved copy. If it doesn't find the program, it waits for the same refresh a chat start would, so several chats started at once share one reread. An agent that is installed but too old is refused straight away, without a reread.
  • The model list a new chat loads ("which models can this agent use") finds the agent the same way: if its program isn't found, it waits for the same reread and looks again. Before, the first chat after installing Claude could still say "Claude wasn't found on the computer running this chat", because the model list looked first with the old copy and never looked again.
  • If a refresh fails (your shell setup hangs or errors), the last good copy is kept and the next refresh tries again.
  • Windows: the PowerShell profile (Windows' shell setup) is read until a read succeeds. Before, a first read that failed was kept until restart; now PowerShell is tried again 10 seconds after the last attempt, however many chats start in between. A chat start that doesn't find its program rereads only the saved PATH right away, not PowerShell. The PATH that Windows saves in its settings, which is where installers add themselves, is laid over the profile on every chat start (reread at most every 30 seconds, and when a program is missing, a reread that began after the start looked); after a successful profile read, PowerShell doesn't run again. A Windows server reads that through Windows' reg.exe, because the server doesn't ship the add-on the desktop uses.

What you'll notice: after you install or change something on the remote machine, the next new chat picks it up without restarting Orca, including the first chat after installing an agent.

What changed in the code

  • Agent connection defaults (problem 1): applyThisAppCli in src/main/runtime/structured-session-child-identity-env.ts sets ORCA_USER_DATA_PATH to the server's data folder and ORCA_ENVIRONMENT, ORCA_PAIRING_CODE and ORCA_REMOTE_PAIRING to empty strings. Empty rather than removed, because every agent process (Claude, Codex, the agents that speak the Agent Client Protocol, Pi) is started over the server's own environment, and a removed variable would come back from there; an empty one wins. The orca command already treats an empty value as unset, so the command itself is unchanged.
  • Windows launcher (run_server_cli in native/windows-cli-launcher/src/main.rs): clears ORCA_WINDOWS_PACKAGED_CLI_LAUNCHER, a marker the desktop's own launcher sets. A server started with orca serve from the desktop's command line inherits that marker, and then every agent's orca orchestration ask failed with "The packaged Orca launcher did not provide a valid resume command".
  • Claude's lookup order (problem 2): Claude resolves its program after the shell setup and your configured overrides are applied, through the same resolver Codex uses.
  • Refreshing the shell setup (problem 3):
    • src/main/startup/login-shell-environment.ts is the one place the read shell setup lives: the last good copy, the reread in progress, and which result wins (the newest reread started; a failed read never replaces a good one). A caller can ask for a reread that started no earlier than a given time, for the saved copy with a background reread once the last attempt is older than 10 seconds, and on Windows to keep a loaded profile for good.
    • src/main/runtime/structured-agent-shell-environment.ts holds only the policy on top: the 10-second interval, the Windows saved PATH laid over the profile, and your settings. Chat starts, the pre-create check and AI commit messages all read through it, and refreshAfterMissingProgram asks for a reread that began after the start looked.
    • resolveAgainAfterRefreshOnMissingProgram in src/main/native-chat/structured-agent-command-resolution.ts is the one place that asks for that refresh. Each agent's one lookup of its program and environment calls it (resolveClaudeChildEnvSources, resolveCodexStructuredInvocation, resolveAcpLaunchInvocation, resolvePiRpcInvocation), and both a chat's launch and its model list go through that lookup.
    • structuredAgentSupportsLaunch (the pre-create check) finds the agent's program through that same function, so only a missing program waits for the refresh; the version check then runs on what was found.

Why this approach

  • Use the variables orca already reads. An earlier version of this PR added a separate "this server's own" setting that made orca ignore ORCA_ENVIRONMENT everywhere on the server, including a person's deliberate one. Supplying the agent's defaults at the point the server starts it is the common pattern, needs no change to the command, and leaves people's settings alone.
  • Use main's delivery of orca. Main already ships orca to managed servers (Provide Orca CLI on managed Linux and macOS servers #26539, fix(orcad): provide the orca CLI in Windows managed-host terminals #27324). This PR adds nothing to how it is delivered.
  • Refresh in the background; wait only on a miss. Re-reading the shell setup on every chat start made each start wait about 2 seconds with a slow profile and 5 with a hung one. Serving the saved copy keeps starts instant, and waiting only when the program is missing fixes the first chat after an install without slowing normal starts. Making agent detection also refresh the launch copy was the alternative; it was not chosen because a chat can start without detection running (from the command line or the phone), and it would still race the refresh.

Differences from the common pattern

  • Matches — how agents reach Orca. The server supplies the agent's connection defaults through the variables the command already reads, at the moment it starts the agent, and a person's terminal follows its own settings.
    • Known limit, shared by the common pattern: if an agent itself starts a login shell that exports a target again (Codex runs commands through zsh -lc), orca in that shell follows it. A target named on purpose is unaffected.
  • Intended — agents reach the server through its local, authenticated connection rather than being handed a network address. Orca's command-line tool already works that way on every machine.
  • Matches — refreshing. A saved copy, a 10-second refresh, the old copy while refreshing, and the last good copy kept when a refresh fails.
  • Intended — one extra refresh when a program is missing. The common pattern has none, so its first chat after an install can fail. Here a missing program waits at most 5 seconds for one shared refresh. Nothing changes for a program that is found.
  • Intended — the whole shell setup, not just PATH. Orca reads all your shell setup's variables, and your "inherit shell environment" setting decides which reach chats. That's main's design.
  • Intended — Windows reads the PowerShell profile until it loads once. The common pattern starts no shell there. Keeping main's behaviour keeps PowerShell profile variables in chats, at the cost of PowerShell runs until one succeeds (at most one per 10 seconds, never per chat), then none.

Linked Issue

Part of the maintainer-requested managed SSH native-chat fixes; no separate issue. Builds on #26539.

Visual Proof

No rendered UI changes; a live check over SSH shows the user-facing result. Recorded with the build of 81b1f10805f. The only later commit (88aadd422ee) changes how a relative PATH folder and Windows rereads are handled, which this Linux check doesn't exercise.

Setup (Docker). The desktop app connected over SSH, as a normal (non-root) user, to a managed Orca server in a Linux container. Before connecting, the user's ~/.profile got a stray ORCA_ENVIRONMENT=qa-bogus-environment and a bogus pairing code. Claude wasn't installed. Then the real Claude Code CLI (2.1.296) was installed while the server was running, on the login-shell PATH only (a plain sh can't find it). It was not signed in on purpose; no credentials were copied.

1. The first new Claude chat after the install starts Claude. It shows Claude's own "isn't signed in" message, which proves Orca found and launched it. There is no "Claude wasn't found" anywhere; on main both the chat and the banner above it said Claude wasn't found until the server restarted.

first chat after install

2. A person's terminal on the server honours their own setting. With the profile's stray values, orca status uses them, so it reports "Unknown environment: qa-bogus-environment", or that both a pairing code and an environment are set. With them cleared, it reaches this server (ok: true, the server's process id, connected).

terminal honours the person's setting

3. The agent's own orca reaches its own server (checked from the process, no screenshot). In the running Claude process, ORCA_ENVIRONMENT, ORCA_PAIRING_CODE and ORCA_REMOTE_PAIRING are present but empty, overriding the profile. ORCA_USER_DATA_PATH and ORCA_CLI_COMMAND are set. With exactly that environment, orca status --json returns ok: true with the managed server's process id, connected.

4. A profile change reaches the next chat without a restart. After adding export QA_LOGIN_MARK=after-change to ~/.profile, the next new chat's Claude had it.

Real x86_64 Linux host with a signed-in Claude. The dev server deployed and ran. Check 3 passed there too, on the real agent process. A real Claude reply could not be shown: that machine's Claude login is currently broken outside Orca as well (a plain claude -p from its login shell fails), so that part is not evidence either way.

Testing

Agents reach their own server; people's settings work (problem 1):

  • For each of ORCA_ENVIRONMENT, ORCA_PAIRING_CODE and ORCA_REMOTE_PAIRING, inherited by both the shell copy and the server's own environment, the agent's final environment (through the real spawn merge) names the server's data folder and leaves all three empty. Fails without the change.
  • A real spawned Codex child, started by a server whose own environment carries all three, sees them empty. Fails without the change, and also fails if they are removed instead of emptied, which confirms the server's environment would bring them back.
  • orca itself: with an agent's empty values it stays on its own server, while --environment and --pairing-code still reach a paired server; with a person's ORCA_ENVIRONMENT, ORCA_PAIRING_CODE or ORCA_REMOTE_PAIRING it goes where that names, as on main. --environment and --host runtime:<id> routing also checked through the command entry point.
  • The managed server's launcher passes a person's ORCA_ENVIRONMENT and pairing code through and adds only the data folder. Fails against the previous head, whose launcher added the "this server's own" setting.
  • Packaged check (server slot built in CI's "Headless Node server" run 38202754321; passed on macOS, Linux and Windows): an agent's orca status reaches the running server despite stale target variables, and a person's orca status with ORCA_ENVIRONMENT=stale-shell-selection is refused as "Unknown environment: stale-shell-selection", meaning their setting is honoured.
  • Windows launcher: the server mode clears the desktop marker (build-windows-cli-launcher test).

First chat after an install (problem 3):

  • With the shell copy read before the install, the first launch resolves the newly installed program for Claude, Codex and a Command setting (Grok), each through its real resolver and the real saved-copy logic; Pi through its launch resolver. All fail against the previous head, which reported the bare name (not found).
  • A program the saved copy already finds never waits for a refresh.
  • Two misses share one refresh; a hung shell holds a miss for exactly one shell time limit (5 seconds), and the saved copy stays.
  • OpenCode's pre-create check rereads the shell once and accepts an agent only the new PATH reaches. Fails without the change.
  • Windows, simulated on the real merge and capture (only the process runner and the powershell.exe child are faked): a PowerShell profile that fails once is loaded on the next refresh, after which refreshes run no PowerShell. Fails against the previous head.
  • A missing program is reread when PATH has a relative entry that doesn't hold it, and not when the entry does (checked from the chat's folder); on Windows, a missing program waits for a saved-PATH read that began after it looked, not one already running from before the install. Both fail against the previous head.
  • Earlier coverage kept: one login-shell run at startup, the old copy served while refreshing, a failed refresh keeping the last good copy, Windows reg.exe reads with and without a saved user Path, and the measured waits (2-second profile [2053, 0, 2, 0] ms, hung profile [5003, 0, 1, 0] ms).

Checks on this head:

  • Targeted tests: every test file this PR touches, plus the CLI routing, agent environment, login-shell, terminal environment, launch-resolution and pre-create check tests, and main's tests for the files returned to main (167 files, 1716 passed, 10 skipped).

  • Typecheck: node, cli and web clean.

  • Real Claude CLI suite: 13/13 (Claude's launch resolver changed).

  • orca-ci-checks --no-typecheck: 24/25; the one failure is the local typecheck:ci runner, which needs a bun check script this machine lacks. Typecheck passes on its own (above).

  • CI on ff3e9d9183a: 24 passed, 0 failed. SSH Windows hosts (run 38202750986): all four cells passed. Headless Node server (run 38202754321): all 11 jobs passed.

Not verified on a real Windows host (CI runners only):

  • The reg.exe fallback and the PowerShell retry on a real Windows server.

Review

Earlier heads went through several full review rounds. This head reworks problem 1 to the common pattern and adds the two refresh fixes; #26450 should merge first, because without it Claude chats on a managed server can't start, with or without this PR.

Agent skill upstream boundary

  • Not applicable: this change copies no upstream skill-installer source or resources.

Notes

No new RPC parameters, stream frames, settings, credentials, or stored native-chat data. Old server slots without the CLI still start, as in #26539. Folder workspaces do not need Git.

Checklist

  • Focused on agent CLI ownership and launch environment on the execution host
  • Explained the problem, user-facing change, mechanism, and alternatives
  • Visual proof redone for this head
  • Self-reviewed for correctness and host ownership
  • Cross-platform, SSH/remote, WSL, folder and mixed-version impact considered
  • Local checks passed (typecheck, targeted tests, repo static checks)

Main now bundles the CLI into the server slot and writes a profile launcher
on Unix. Drop this branch's parallel CLI packaging, slot launchers, and
build-time version; keep the owning-host binding (now exported by main's
launcher), the Windows native launcher on main's layout, the Claude command
order, and the refreshed shell environment.
… background

Only the first chat start waits for the login shell; later starts get the last
snapshot and refresh it in the background once it is 10 s old. On Windows hosts
without the registry addon (managed server slots), the saved PATH is reread
with reg.exe. The Windows server CLI launcher stager now checks the target
architecture, and the CLI help says ambient selectors are ignored inside a
managed server.
…he Windows profile once

A login-shell capture now says whether the shell produced the env or Orca fell back
to its own, and only a real capture replaces the snapshot. Windows loads the
PowerShell profile once per process and refreshes only the saved PATH. The reg.exe
fallback queries the whole key so a missing user Path no longer drops the saved PATH.
The first capture starts when the resolvers are created.
…-remote-launch-env

# Conflicts:
#	src/main/orcad/orcad-cli-launcher.test.ts
#	src/main/runtime/structured-agent-shell-environment.ts
It only types the chat command resolver it injects; main's launch-routing guard (#26801) matched the name.
… the first run

- Any settled cached entry, a fallback included, now serves other readers while a forced capture
  runs; only a real capture replaces it.
- Native chat's first capture shares a run a startup reader already began, so the login shell
  runs once per process at startup on macOS/Linux.
- Type the injected Claude resolver without naming resolveClaudeCommand.
- Cover the runtime's Claude command options, the owning-host --host check, and the PTY flag drop.
The persistent terminal daemon merges its own environment into every pane, so an inherited
ORCA_CLI_OWNING_HOST reached desktop panes even though main strips it from the spawn env.
Drop it with the other per-launch CLI variables.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary (final head 9d83dca4b8d)

What this PR fixes. Agents that native chat starts on a managed SSH server had three problems:

  • orca on Windows servers: agents there had no working orca command (main's Provide Orca CLI on managed Linux and macOS servers #26539 covered Linux and macOS only).
  • Stray selectors: on any server, a stray ORCA_ENVIRONMENT or pairing code in the host's shell profile could send an agent's orca calls to a different machine.
  • Claude command lookup: a custom Claude command available only on the login PATH was refused, because Claude looked up its command before loading the login environment.
  • Host changes: installing or fixing an agent command on the host had no effect until Orca restarted.

After this PR, orca inside a managed server always talks to that server unless you name another one on purpose. Claude finds commands the way the other agents do. New chats pick up host changes without a restart.

Earlier decision. Main's #26539 delivers the CLI differently from this PR's earlier version. Main's design is kept; this PR adds only what main lacks: Windows, the owning-server binding, Claude's command order, and keeping the environment current.

Final rounds

  • Main sync (c518623be7c): only imports moved to @orca/process-host. The PR's own diff is otherwise unchanged; I compared it before and after.
  • Fresh full review of that head: nothing blocking. It found two small regressions against main, both fixed in 9d83dca4b8d with tests that fail first:
    • While a refresh ran, a cached fallback environment made other readers wait 5–10 seconds. Those readers are the pre-start check for OpenCode, OMP and Pi, and AI commit messages.
    • The login shell ran twice at startup instead of once.
  • Also in that round:
    • new tests for lines no test covered, including the custom-Claude-command wiring;
    • the Claude launch file no longer needs an entry on the launch-routing test's reviewed list;
    • one extra fix found along the way: the persistent terminal daemon copied an inherited server-binding flag into every new terminal it opened, and now drops it.
  • I read the cache change myself. A failed first capture still fills the empty snapshot, so the next refresh retries the shell. Nothing gets stuck.

Checks on 9d83dca4b8d

  • PR CI: all green, and MERGEABLE (clean against current main).
  • SSH Windows hosts, default cells: 4/4 passed.
  • Headless Node server: passed on every platform, including both packaged CLI integrations on Windows x64 and arm64.
  • Packaged Windows serve-switch: passed.
  • Named @orcad-cli-* Windows cells: relay-kept passed. managed and convert fail identically on main (run 37916705160), so they are pre-existing.
  • Local: 243 targeted tests, node and cli typecheck, real Claude suite 13/13, orca-ci-checks 23/23, packaged integrations 2/2.

Live SSH QA (screenshots in the body). This used a stand-in Claude on a Linux container.

  • Installing Claude while Orca runs: Claude was put on the login PATH only. With no restart, the menu offers it and the next chat works.
  • The agent's own orca: it reaches its own server despite a stray ORCA_ENVIRONMENT.
  • Host profile changes: they reach the next chat without a restart.
  • A person in the server's terminal: reaches the same server; an explicit --environment is still honoured.

These chat scenarios ran on this PR merged locally with #26450. Without #26450, no Claude chat can start on a managed server on main either. A run on this PR alone passed every scenario that doesn't start a Claude chat.

Known gaps (in the body)

  • First chat right after installing Claude says it wasn't found. The menu already offers Claude, but that start used the older environment snapshot and began a refresh; the next chat works. Intended: matches the common pattern of not forcing a fresh environment read when a command lookup fails. It could be removed by waiting for one refresh before reporting "not found".
  • Temporary, Windows: the pre-start check for OpenCode, OMP and Pi, and AI commit messages, keep their own login-shell capture. They don't see saved-PATH refreshes, and PowerShell can run twice per process. Follow-up not yet filed.
  • Temporary, Windows: a terminal left open across two server updates can lose orca once the old version's folder is removed. Follow-up not yet filed.
  • Not verified on real Windows: the reg.exe fallback and real users' profiles.

Merge order: #26450 first (it is being synced with main now), then this.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 10, 2026 12:44
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a0bfedc0-4d04-4307-aaa3-c7e1d8bf3810
📥 Commits

Reviewing files that changed from the base of the PR and between 0e2ad13 and 0da44d4.

⛔ Files ignored due to path filters: 1
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing: 8
  • config/scripts/build-windows-cli-launcher.test.mjs
  • native/windows-cli-launcher/src/main.rs
  • src/main/native-chat/structured-agent-command-resolution.ts
  • src/main/runtime/orca-runtime-get-worktree-ps.ts
  • src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts
  • src/main/runtime/structured-agent-session-runtime.ts
  • src/main/startup/login-shell-environment.ts
  • src/shared/orcad-artifacts.test.ts

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

📝 Walkthrough

The changes add refreshable shell and Windows PATH environments for structured-agent launches and model catalog probes. Command resolution can retry after a missing-program result when a refresh callback is available. Structured child environments clear inherited runtime selector variables, while CLI tests cover explicit routing and packaged launcher behavior. Windows launcher tests also verify that the packaged marker is removed from the server child environment.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 0da44

The WSL launcher fallback remains a bounded concern to confirm before relying on that launch mode. The inspected environment-refresh paths do not establish another merge-blocking issue.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 51.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 78 functions across 68 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 summarizes the primary changes: CLI delivery and launch-environment fixes for managed SSH agents.
Description check Passed The description is detailed and covers the problem, user-visible behavior, implementation, rationale, testing, platform considerations, and known Windows verification gap. It also includes visual evid…
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: 88ac6d06-ebaf-459d-a671-a859e0ecdade
📥 Commits

Reviewing files that changed from the base of the PR and between 180e8c1 and 9d83dca.

📒 Files selected for processing (46)
  • .github/workflows/node-server-tests.yml
  • config/scripts/build-orcad.mjs
  • config/scripts/build-windows-cli-launcher.mjs
  • config/scripts/build-windows-cli-launcher.test.mjs
  • config/scripts/node-server-test-paths.mjs
  • config/scripts/orcad-windows-cli-launcher.mjs
  • config/scripts/orcad-windows-cli-launcher.test.mjs
  • native/windows-cli-launcher/src/main.rs
  • src/cli/index-local-command-routing-flags.test.ts
  • src/cli/index-test-harness.ts
  • src/cli/index.ts
  • src/cli/root-help-text-secondary.ts
  • src/cli/runtime/client-execution-host.test.ts
  • src/cli/runtime/client.ts
  • src/main/claude/claude-structured-child-env.test.ts
  • src/main/claude/claude-structured-child-env.ts
  • src/main/claude/claude-structured-launch-resolution.ts
  • src/main/cli/cli-dev-launcher.ts
  • src/main/cli/orca-cli-child-path.test.ts
  • src/main/cli/orca-cli-child-path.ts
  • src/main/codex/codex-structured-child-environment.test.ts
  • src/main/codex/codex-structured-session-adapter.test.ts
  • src/main/daemon/pty-subprocess-env-inheritance.test.ts
  • src/main/daemon/pty-subprocess/spawn-environment.ts
  • src/main/ipc/pty-daemon-spawn-agent-home-env.test.ts
  • src/main/ipc/pty/host-env/assembly.ts
  • src/main/orcad/orcad-cli-launcher.integration.test.ts
  • src/main/orcad/orcad-cli-launcher.test.ts
  • src/main/orcad/orcad-cli-launcher.ts
  • src/main/orcad/orcad-cli-orchestration.integration.test.ts
  • src/main/pty/windows-environment-path.ts
  • src/main/pty/windows-path-registry-reader.test.ts
  • src/main/pty/windows-path-registry-reader.ts
  • src/main/runtime/orca-runtime-get-worktree-ps.ts
  • src/main/runtime/orca-runtime-structured-launch-settings-wiring.test.ts
  • src/main/runtime/structured-agent-session-runtime.ts
  • src/main/runtime/structured-agent-shell-environment.test.ts
  • src/main/runtime/structured-agent-shell-environment.ts
  • src/main/runtime/structured-claude-runtime-adapter.ts
  • src/main/runtime/structured-session-child-identity-env.test.ts
  • src/main/runtime/structured-session-child-identity-env.ts
  • src/main/startup/login-shell-environment-cache.test.ts
  • src/main/startup/login-shell-environment.ts
  • src/shared/cli-execution-host-env.ts
  • src/shared/orcad-artifacts.test.ts
  • src/shared/orcad-artifacts.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 thread src/main/cli/orca-cli-child-path.ts Outdated
…-remote-launch-env

# Conflicts:
#	.github/workflows/node-server-tests.yml
#	config/scripts/build-windows-cli-launcher.mjs
#	config/scripts/orcad-windows-cli-launcher.mjs
#	config/scripts/orcad-windows-cli-launcher.test.mjs
#	native/windows-cli-launcher/src/main.rs
#	src/main/cli/orca-cli-child-path.test.ts
#	src/main/orcad/orcad-cli-launcher.test.ts
#	src/main/orcad/orcad-cli-launcher.ts
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Synced with main again (head 547f29a21c4). main moved after this PR was marked ready, and it had started to conflict.

What changed on main. #27324 gave Windows managed servers their own orca command: a profile-level orca.exe plus a config file that switches the launcher into server mode. That was this PR's Windows piece, so main's design wins:

  • The conflicting launcher files took main's version, and this PR's per-version-folder Windows launcher is gone. Its "a terminal left open across two server updates can lose orca" limitation goes with it.
  • On Windows, this PR now only adds two things to main's server mode (run_server_cli): it sets ORCA_CLI_OWNING_HOST=1, and it clears the desktop marker ORCA_WINDOWS_PACKAGED_CLI_LAUNCHER. With that marker inherited, orchestration would demand a bare orca resume command.
  • The guard that keeps WSL panes off the Windows launcher stays, since main doesn't have it.
  • Files that merged without conflict markers but no longer matched main's layout were fixed. These were a duplicate filename constant, a launcher test for the old layout, and both packaged CLI integrations.

I checked myself:

  • The Rust change is those two lines.
  • orcad-cli-launcher.ts is now identical to main's.
  • The Linux/macOS launcher (cli-dev-launcher.ts) still exports the binding flag.
  • Agents still get the flag through bindOrcaCliToExecutionHost. The live QA showed ORCA_CLI_OWNING_HOST=1 in the agent's environment.

Checks

  • Typecheck: node, cli and web clean. 351 targeted tests pass, and no shared file is over its line limit.
  • CI: 38 passed, 13 skipped, 0 failed. That includes all four SSH Windows hosts cells, persistence on both Windows runners, the packaged Windows serve switch, and Windows native smoke. MERGEABLE.
  • Rust: compiled by CI's Windows jobs only.
  • One unrelated formatting change: the commit hook reformatted one long line from main in subagent-tracker.test.ts. It's formatting only.

The PR body is updated to match. The earlier live SSH QA (Linux container) still describes the user-facing behaviour, since the Linux path is unchanged by this sync.

…art the first chat after an install

The native-chat agent environment now sets the owning server's ORCA_USER_DATA_PATH and
empty ORCA_ENVIRONMENT / ORCA_PAIRING_CODE / ORCA_REMOTE_PAIRING, which the CLI already
reads as unset; empty rather than deleted, because the spawn merges the launch overlay
over the server's own env. The ORCA_CLI_OWNING_HOST flag, the CLI's skip of ambient
selectors, the launchers' export of it, the terminal clean-up, the help-text exception
and the WSL launcher guard are gone, so a person's terminal follows their own settings
as on main.

A launch that finds no program in the shell snapshot waits for one shared refresh (at
most one shell timeout) and resolves again; create support rereads the shell once
before refusing. On Windows a failed PowerShell profile load is retried on the next
refresh instead of being kept until restart.
@brennanb2025
brennanb2025 marked this pull request as draft October 12, 2026 04:08
The pre-create check for OpenCode, OMP and Pi forced its own login-shell
capture on every refusal: not shared with launches, one per concurrent
check, triggered by an installed-but-too-old version too, and an older
capture finishing last could overwrite a newer cached environment. On
Windows, every launch miss also reran a failed PowerShell profile load,
bypassing the 10-second limit.

- One saved shell environment per host: chat launches, their misses, the
  pre-create check and AI commit messages read it and share one refresh.
- The pre-create check resolves the agent's program the way a launch does,
  so only a missing program waits for the refresh; registrations declare
  how to resolve their launch and check its version.
- A forced login-shell capture joins one already running for the same
  shell, so captures cannot land out of order.
- On Windows, a miss within 10 s of a failed PowerShell load rereads only
  the saved PATH.
Main's #27204 moved every native-chat agent onto one composed launch path.
This PR's launch-side changes are carried onto it:
- The composer's sources take the runtime's refreshAfterMissingProgram, and
  the launch basis exposes it; after a refresh its environment reads return
  the refreshed environment. Each agent's part (Claude, Codex, ACP, Pi)
  resolves its program through resolveAgainAfterRefreshOnMissingProgram with
  it, so a launch that finds no program waits for the one shared refresh.
- Claude resolves its command from the launch's own environment.
- The parity snapshot gains the three remote selectors the seal sets empty;
  nothing else in it changed.
Review found two more races in the reread-on-miss machinery, both from the
host wrapper keeping its own snapshot beside the login-shell cache:
- A launch that found no program could join a reread that started before the
  agent was installed, and spend its one retry on the old PATH.
- On Windows, saved-PATH rereads renewed the snapshot's age, so a failed
  PowerShell profile was never retried while launches kept missing.

The login-shell cache is now the only owner of the captured environment, the
run in progress and which result wins (the newest run started; a fallback
never replaces a captured env). It takes three options instead of `force`:
- startedAfter: a lookup that found nothing waits for a run that started no
  earlier than the lookup did, joining one if it has, else starting one.
- refreshAfterMs: serve the last result and rerun in the background once the
  last attempt is older.
- keepCaptured: a captured env is kept for good; only a fallback is rerun
  (Windows' PowerShell profile).

The host wrapper keeps only policy: the 10 s interval, on Windows the saved
PATH laid over the profile env on every read (a miss forces a fresh registry
read), and the settings overlays. Its snapshot, pending promise, staleness and
Windows "loaded" bookkeeping are gone. A lookup's start time travels with the
miss (resolveAgainAfterRefreshOnMissingProgram passes it).
The previous commit read an injected environment on every acquisition, which
broke main's 'caches shell exports but re-reads configured overrides for a
resume' contract. It is read once when the resolvers are made again.
Live QA: the first new chat after installing Claude showed "Claude wasn't
found on the computer running this chat". The chat launch itself reread the
login shell and started the new binary, but Claude's model-list check ran
first, with the environment read before the install, and never retried:
catalog probes were deliberately built without the miss refresh.

The retry now lives in each agent's one shared lookup of its binary and
environment, which a launch and a model listing both call:
resolveCodexStructuredInvocation, resolveAcpLaunchInvocation, a new shared
resolvePiRpcInvocation, and resolveClaudeChildEnvSources (which already had
it). Each catalog probe is given the runtime's refreshAfterMissingProgram,
and the catalog context no longer strips it, so a listing and a launch
reread the same single-owner environment the same way.
…e extra wait cap

CI on the merge with main failed main's new codex-workspace-relative-path
test (#26684), which mocks the login-shell module with only
resolveLoginShellEnvironment: the wrapper called captureLoginShellEnvironment
and imported the spawn-timeout constant. The wrapper only needs the env, so
it reads through resolveLoginShellEnvironment again, as on main.

The miss wait no longer races its own 5 s timer: the shell run it waits for
is already killed at the spawn timeout, so the second cap was a guard over
the same bound. The constant goes back to private, as on main.
…-remote-launch-env

# Conflicts:
#	src/main/runtime/structured-agent-runtime-registrations.ts
…-remote-launch-env

# Conflicts:
#	src/main/runtime/structured-agent-launch-composition.ts
#	src/main/runtime/structured-agent-session-runtime.ts
…l timeout

The bound lives in the one owner: the shell run is killed at the spawn
timeout (5 s) and settles to a fallback, which never replaces a captured
snapshot. A mutation raising that timeout fails this test.
Main's #26684 runs Codex through a workspace-relative PATH entry (`bin`,
`node_modules/.bin`). The lookup searches such an entry against Orca's own
directory and returns the bare name; the spawn then finds the program in the
chat's directory. Counting that bare name as missing made every such launch
wait for a login-shell reread.

The retry now fires only when the lookup truly found nothing: a bare name back
from a PATH whose entries are all absolute. Each agent's one lookup tells it
which PATH it searched.
…Windows miss waits for a saved PATH read started after it looked

A bare name with a relative PATH entry is checked from the launch's working
directory, so an agent in neither entry still gets its one reread. On Windows the
miss no longer joins a registry read that began before the lookup.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary (head 0e2ad132ae2)

What this fixes, for chats on a remote machine reached over SSH (Orca's managed server):

  1. Agents reach their own server. A stray ORCA_ENVIRONMENT or pairing code in the remote machine's shell profile no longer sends the agent's own orca commands (coordinator messages, task completion) to another Orca. The server sets those three variables to empty for every chat agent, the same way it already supplies other defaults. A person's own terminal still honours their setting normally. The old ORCA_CLI_OWNING_HOST flag is gone.
  2. Claude installed after the server started works on the first new chat. Before, both the chat and the banner above it said "Claude wasn't found" until the server restarted.
  3. Profile changes reach new chats without a restart.

How it was reviewed

  • Six rounds of independent code review. Round 4 and 5 findings were timing races in the shell-environment refresh. The response was to simplify: one place now owns the saved environment, giving 15 fewer production lines than the round-3 version, rather than more guards.
  • Later fixes:
    • the model-list check now uses the same environment and the same single reread as starting a chat;
    • a relative PATH folder counts only if it holds the program;
    • a Windows reread never reuses a read that began before the lookup;
    • a stuck login shell holds a start for at most 5 seconds.
  • The final pass on 88aadd422ee approved, with no blocking issues and nothing removable. Since then, main has been merged with no conflicts.

Live check (see Visual Proof; build of 81b1f10805f). Run in a Linux container over SSH, with the real Claude CLI installed after the server started and not signed in:

  • the first chat launched Claude;
  • the agent's environment had the routing variables empty, and its orca status reached its own server;
  • the person's terminal honoured ORCA_ENVIRONMENT;
  • a profile change reached the next chat.

On a real x86_64 host, the agent's orca status reached its own server. That machine's Claude login is broken outside Orca, so no real reply could be shown there.

CI: green on 0e2ad132ae2. Local: targeted suites pass (22,118 tests); tc:node passes.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 13, 2026 02:27

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · 🩺 Stability & Availability · windows-environment-path.ts:265-270

src/main/pty/windows-environment-path.ts:265-270
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The windowsHide concern is refuted. The runProcess path resolves spawn options through spawn-resolution.ts, which sets windowsHide: true.

The encoding concern remains supported: runProcess decodes captured stdout as UTF-8, while the reg.exe fallback supplies Windows console-code-page output. Non-ASCII PATH entries can therefore be decoded incorrectly and become unusable. Convert the registry output using the applicable Windows code page before parsing it, or use a Unicode-safe registry query path.

Source: Learnings


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0b937392-ce08-4139-848e-12e7d9f8bcb4
📥 Commits

Reviewing files that changed from the base of the PR and between 547f29a and 0e2ad13.

⛔ Files ignored due to path filters (1)
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (42)
  • config/scripts/build-windows-cli-launcher.test.mjs
  • config/scripts/node-server-test-paths.mjs
  • native/windows-cli-launcher/src/main.rs
  • src/cli/index-local-command-routing-flags.test.ts
  • src/cli/runtime/client-execution-host.test.ts
  • src/main/acp/acp-model-catalog-probe.ts
  • src/main/acp/acp-structured-launch-resolution.ts
  • src/main/claude/claude-structured-child-env.ts
  • src/main/claude/claude-structured-launch-resolution.ts
  • src/main/codex/codex-structured-child-environment.test.ts
  • src/main/codex/codex-structured-launch-resolution.ts
  • src/main/codex/codex-structured-session-adapter.test.ts
  • src/main/native-chat/structured-agent-command-resolution.ts
  • src/main/orcad/orcad-cli-launcher.integration.test.ts
  • src/main/orcad/orcad-cli-launcher.test.ts
  • src/main/orcad/orcad-cli-orchestration.integration.test.ts
  • src/main/pi/rpc-launch-resolution.test.ts
  • src/main/pi/rpc-launch-resolution.ts
  • src/main/pi/rpc-model-catalog-probe.ts
  • src/main/pi/rpc-runtime-registration.test.ts
  • src/main/pi/rpc-runtime-registration.ts
  • src/main/pty/windows-environment-path.ts
  • src/main/runtime/orca-runtime-get-worktree-ps.ts
  • src/main/runtime/structured-agent-install-refresh.test.ts
  • src/main/runtime/structured-agent-launch-composition.test-support.ts
  • src/main/runtime/structured-agent-launch-composition.ts
  • src/main/runtime/structured-agent-launch-support.test.ts
  • src/main/runtime/structured-agent-launch-support.ts
  • src/main/runtime/structured-agent-model-catalog-discovery.test.ts
  • src/main/runtime/structured-agent-model-catalog-discovery.ts
  • src/main/runtime/structured-agent-model-catalog-first-frame.test.ts
  • src/main/runtime/structured-agent-runtime-registrations-acp.test.ts
  • src/main/runtime/structured-agent-runtime-registrations.ts
  • src/main/runtime/structured-agent-session-runtime.ts
  • src/main/runtime/structured-agent-shell-environment.test.ts
  • src/main/runtime/structured-agent-shell-environment.ts
  • src/main/runtime/structured-claude-runtime-adapter.ts
  • src/main/runtime/structured-session-child-env.test.ts
  • src/main/runtime/structured-session-child-env.ts
  • src/main/startup/login-shell-environment-cache.test.ts
  • src/main/startup/login-shell-environment.test.ts
  • src/main/startup/login-shell-environment.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.

…-remote-launch-env

# Conflicts:
#	src/main/native-chat/structured-agent-command-resolution.ts
#	src/main/startup/login-shell-environment.ts
#	src/shared/orcad-artifacts.test.ts
@brennanb2025
brennanb2025 merged commit 0504670 into main Oct 14, 2026
76 of 78 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/ssh-chat-remote-launch-env branch October 14, 2026 05:08
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