Skip to content

Make cross-machine pairing recoverable when the host advertises loopback - #11083

Open
puze8681 wants to merge 3 commits into
stablyai:mainfrom
puze8681:feat/pairing-endpoint-override
Open

Make cross-machine pairing recoverable when the host advertises loopback#11083
puze8681 wants to merge 3 commits into
stablyai:mainfrom
puze8681:feat/pairing-endpoint-override

Conversation

@puze8681

Copy link
Copy Markdown

Problem

Pairing a second machine goes through Settings → Runtime Environments → Share this Orca server. Its address picker defaults to 127.0.0.1, and the hint below it renders as static muted copy that reads identically whichever address is selected. Nothing marks the one selection that produces a link no other device can open.

The result is a link whose credentials are correct but whose endpoint points at the receiving machine's own loopback. Recovering from it meant base64-decoding the offer, rewriting endpoint, and re-encoding by hand — environment add accepted only --name and --pairing-code.

Two commits: one makes the mistake recoverable, one makes it less likely.

environment add --endpoint <host>

Redirects a pairing offer to a reachable address while keeping the deviceToken and pinned public key the host issued.

$ orca environment add --name peer --pairing-code "$CODE" --json
    "endpoint": "ws://127.0.0.1:6768"          # unchanged without the flag

$ orca environment add --name peer --pairing-code "$CODE" --endpoint 100.64.0.2 --json
    "endpoint": "ws://100.64.0.2:6768"         # port inherited from the offer

$ orca environment add --name peer --pairing-code "$CODE" --endpoint wss://desk.example.com/runtime --json
    "endpoint": "wss://desk.example.com/runtime"

$ orca environment add --name peer --pairing-code "$CODE" --endpoint 0.0.0.0 --json
    "message": "Invalid --endpoint \"0.0.0.0\". Use a reachable hostname, host:port, IPv4/IPv6 literal, or ws(s):// URL. ..."

Resolution reuses resolveAdvertisedPairingEndpoint, so the flag accepts exactly the forms the "Share this Orca server" address field accepts, and rejects wildcard bind addresses no client can dial. No second address grammar was introduced.

src/cli reaching into src/main follows the existing pattern (handlers/agent-hooks.tsmain/agent-hooks/…); the module is added to the explicit allowlist in config/tsconfig.cli.json.

Selection-aware loopback warning

  • The picker hint now tracks the selection — loopback gets warning emphasis, any other address states which host the peer must reach.
  • Generated links carry their own warning, keyed to the address the link was minted for rather than the current picker value, so moving the picker afterwards cannot make the warning lie.
  • isLoopbackShareAddress() lives in shared/network so the picker and the link row agree: 127.x, localhost, ::1, [::1], and ws(s):// forms.

The warning uses typography and an icon rather than a new color. A loopback address is a valid choice, not an error, and STYLEGUIDE reserves color for selection, destructive, and git decorations — there is no warning token to reach for.

Verification

  • typecheck (node / cli / web) — clean
  • vitest across src/cli, src/shared/network, src/shared/runtime-environment-store, src/renderer/src/components/settings1434 passed / 170 files
  • oxlint on changed files — clean; oxfmt --check — clean
  • verify-localization-catalog + audit-localization-coverage — pass (2 new keys synced across all 5 locales)
  • check-max-lines-ratchet — pass
  • Built CLI exercised end-to-end against a throwaway ORCA_USER_DATA_PATH (the four cases above)

12 new tests: endpoint override (port inheritance, host:port / ws(s):// forms, credential preservation, wildcard rejection, no-op when omitted), loopback detection, and command-spec coverage asserting --endpoint is accepted on add and rejected on list/show/rm.

Not run: lint:react-doctor:changed. In my checkout oxlint-plugin-react-doctor resolves to 0.2.10 while package.json pins 0.9.1, so config/oxlint-react-doctor.json fails to parse — identically on files this PR does not touch. Unrelated to these changes, but flagging it since the pre-commit hook depends on it.

🤖 Generated with Claude Code

murphy and others added 2 commits July 28, 2026 13:35
A pairing offer embeds whatever address the host advertised. "Share this
Orca server" defaults its address picker to 127.0.0.1, so a link minted
for a LAN or Tailscale peer routinely carries a loopback endpoint that
the receiving machine dials against itself. The credentials are fine —
only the address is wrong — but the CLI had no way to correct it, leaving
hand-editing the base64 offer as the only path.

`environment add --endpoint <host>` redirects the offer while keeping the
deviceToken and pinned public key the host issued. Resolution reuses
resolveAdvertisedPairingEndpoint, so the flag accepts the same host,
host:port, and ws(s):// forms as the address field that produced the
link, inherits the port from the pairing code when omitted, and rejects
wildcard bind addresses no client can dial.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The address picker under "Share this Orca server" defaults to 127.0.0.1,
and its hint rendered as static muted copy that read the same whichever
address was selected. Nothing distinguished the one selection that
produces a link no other device can open, so pairing a second machine
tended to fail only after the link had been copied and pasted.

Make the hint track the selection: loopback gets warning emphasis, any
other address states which host the peer has to reach. Generated links
carry their own warning, keyed to the address the link was actually
minted for rather than the current picker value, so moving the picker
afterwards cannot make the warning lie.

Loopback detection lives in shared/network so the picker and the link
row agree; it covers 127.x, localhost, ::1, and ws(s):// forms. The
warning uses typography and an icon rather than a new color, since a
loopback address is a valid choice and the palette reserves color for
selection, destructive, and git decorations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c9f4545-ab3c-4528-966b-d3ffeb683555

📥 Commits

Reviewing files that changed from the base of the PR and between 78baf19 and f16062f.

📒 Files selected for processing (5)
  • src/cli/handlers/environment.ts
  • src/cli/runtime/environments.test.ts
  • src/cli/runtime/environments.ts
  • src/cli/specs/environment.test.ts
  • src/renderer/src/components/settings/RuntimePairingGeneratorForm.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/cli/specs/environment.test.ts
  • src/cli/runtime/environments.test.ts
  • src/cli/handlers/environment.ts
  • src/cli/runtime/environments.ts
  • src/renderer/src/components/settings/RuntimePairingGeneratorForm.tsx

📝 Walkthrough

Walkthrough

The CLI environment add command now accepts an optional endpoint override, validates it, and stores the resolved endpoint with the paired environment. CLI specifications, help text, compilation inputs, and tests were updated. Shared networking code adds loopback address detection. Runtime pairing generation tracks the address used for generated links and displays reachability or loopback warnings using new localized strings.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change, but it does not follow the required template and is missing Summary, Screenshots, Testing, AI Review Report, Security Audit, and Notes sections. Rewrite the PR description to use the repo template and fill every required section, including screenshots/no visual change, testing checklist, cross-platform review, security audit, and notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: making cross-machine pairing recoverable when loopback endpoints are advertised.
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.

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

🧹 Nitpick comments (1)
src/renderer/src/components/settings/RuntimePairingGeneratorForm.tsx (1)

192-202: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Redundant warning when selection and generated address are both loopback.

When selectedAddress hasn't changed since generating (the common path: pick "This computer", then Generate), both selectionIsLoopback (lines 157-164) and generatedForLoopback (this block) are true, so the user sees two near-identical loopback warnings stacked in the same card. Consider suppressing this banner when selectionIsLoopback is already showing the same message.

♻️ Proposed fix to avoid double messaging
-      {generatedForLoopback && (webClientUrl || runtimePairingUrl) ? (
+      {generatedForLoopback && !selectionIsLoopback && (webClientUrl || runtimePairingUrl) ? (
         <p className="flex items-start gap-1.5 text-xs text-foreground">
           <AlertTriangle className="mt-0.5 size-3.5 shrink-0" />
           {translate(
             'auto.components.settings.RuntimePairingUrlGenerator.generated-loopback-warning',
             'These links were generated for {{address}}, so only this computer can open them. Pick a reachable address above and generate again to pair another device.',
             { address: generatedAddress ?? '' }
           )}
         </p>
       ) : null}

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e5e50c4-f6dc-453c-87cc-203463ff8d11

📥 Commits

Reviewing files that changed from the base of the PR and between badf911 and 78baf19.

📒 Files selected for processing (17)
  • config/tsconfig.cli.json
  • src/cli/handlers/environment.ts
  • src/cli/help.ts
  • src/cli/runtime/environments.test.ts
  • src/cli/runtime/environments.ts
  • src/cli/specs/environment.test.ts
  • src/cli/specs/environment.ts
  • src/renderer/src/components/settings/RuntimePairingGeneratorForm.tsx
  • src/renderer/src/components/settings/RuntimePairingUrlGenerator.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/network/server-share-address.test.ts
  • src/shared/network/server-share-address.ts
  • src/shared/runtime-environment-store.ts

Comment thread src/cli/handlers/environment.ts
resolveAdvertisedPairingEndpoint reads a blank advertised address as
"none given" and defaults to 127.0.0.1. That fallback is right for
`orca serve` with no --pairing-address, but on the override path it
inverted the flag's purpose: `--endpoint " "` rewrote a reachable
ws://10.0.0.5:6768 offer into loopback, silently causing the failure the
flag exists to repair.

Guard both layers. getOptionalStringFlag now measures the trimmed value,
and addEnvironmentFromPairingCode tests endpointAddress for provided-ness
rather than truthiness so an empty string is rejected as invalid instead
of passing as "no override" — direct callers get the same protection as
the CLI.

Also drop the generated-link warning when the picker still sits on the
address the link was minted for; the hint above the picker already says
the same thing, and stacking both read as two separate problems.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@puze8681

Copy link
Copy Markdown
Author

Both CodeRabbit comments addressed in f16062f.

1. Blank --endpoint (src/cli/handlers/environment.ts) — real bug, and the impact was worse than "fails to reject": the blank value was silently applied, downgrading a reachable ws://10.0.0.5:6768 offer to ws://127.0.0.1:6768 — exactly the failure the flag exists to repair. Guarded at both layers (trimmed check in the flag reader, provided-ness rather than truthiness in addEnvironmentFromPairingCode) so direct API callers are covered too. Details in the inline reply.

2. Redundant stacked warning (RuntimePairingGeneratorForm.tsx) — agreed, took the suggested !selectionIsLoopback guard. On the common path (pick "This computer" → Generate) the picker hint already carries the message; two near-identical warnings in one card read as two separate problems.

One correction to my own test in this round: I first asserted that --endpoint= is rejected at parse time. It isn't — only GLOBAL_VALUE_FLAGS (pairing-code, environment) get that guard, so an empty value reaches the handler. The test now asserts what actually holds (parses to '', rejected downstream) rather than what I assumed.

Re-verified: typecheck (node/cli/web) clean, 1438 tests passed / 170 files (+4), oxlint and oxfmt clean, localization and max-lines gates pass, and the four CLI cases re-run against the rebuilt binary — blank rejected, valid override applied, omitted flag still a no-op.

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