Skip to content

feat(browser): drive the running Arc by attaching, never launch a duplicate (PHNX-2399) - #3299

Merged
muqsitnawaz merged 4 commits into
mainfrom
phnx-2399-arc-attach
Aug 30, 2026
Merged

feat(browser): drive the running Arc by attaching, never launch a duplicate (PHNX-2399)#3299
muqsitnawaz merged 4 commits into
mainfrom
phnx-2399-arc-attach

Conversation

@muqsitnawaz

Copy link
Copy Markdown
Contributor

What & why

agents browser on macOS launched a second Arc window/instance instead of driving the user's existing Arc — and could not reliably drive Arc at all. Arc is single-instance: relaunching the Arc binary with a fresh --user-data-dir doesn't start a second debuggable process — macOS routes the launch to the already-open Arc (started with no debug port), yielding a stray window and no CDP endpoint.

This makes an Arc profile attach to the running Arc, and fail loud when that Arc has no debuggable endpoint — it never silently spawns a duplicate.

Closes PHNX-2399.

Behavior change

  • Attach, never launch (drivers/local.ts). For browser === 'arc', connectLocal no longer falls through to launchBrowser. It attaches to the running Arc's CDP endpoint, or throws arcAttachRequiredError — which names the profile, the port, and the one relaunch that fixes it:
    Arc is not exposing a CDP endpoint on cdp://127.0.0.1:9222 for profile "work".
    Arc is single-instance — agents browser attaches to your RUNNING Arc and never
    launches a second one … Quit Arc, then relaunch it with remote debugging:
      open -a Arc --args --remote-debugging-port=9222
    
  • Select a Space (browser.ts). --target-filter (url:/title:) is now accepted for --browser arc, not just --electron. Arc does not expose Spaces as separate CDP contexts, so you bind the profile to a tab that lives in the Space you want; navigate reuses that tab in place (never hijacks a page you're reading).
  • Fail clearly for the one CDP-only op (service.ts). Opening a brand-new tab (Target.createTarget) crashes Arc, so tab-creating verbs fail with an accurate, actionable error. The old message wrongly claimed Arc "exposes no CDP page targets" — it exposes them and honors Page.navigate; only tab creation is unsupported.
  • Viewer message corrected (open-url.ts). Arc still falls back to the OS browser for anything a human reads, because showing a page needs its own fresh tab.
  • Docs (docs/browser.md, new Arc — attach to your running window section) + CHANGELOG.

Verification

Live end-to-end against a real Arc is macOS-only and requires the operator to relaunch Arc with a debug port, so it can't run in CI or on this Linux worker. The real attach contract is unit-tested (the fail-loud path runs against a real socket, exactly like the existing #43 test), and the full browser suite passes:

$ npx vitest run src/lib/browser src/commands/browser.edit.test.ts src/commands/browser.use.test.ts src/lib/open-url.test.ts
 Test Files  35 passed | 1 skipped (36)
      Tests  595 passed | 1 skipped (596)

New tests: drivers/local.test.ts — Arc fails loud (never launches) both when nothing serves CDP on the port and when a non-CDP listener holds it; arcAttachRequiredError names profile/port/relaunch. Updated service.test.ts assertions to the accurate messages.

Follow-up

  • Companion fleet-guidance update (phnx-labs/.agents skills/browser/browser-use.md) to teach the attach model — linked separately.
  • Name-based Space switching would need Arc's AppleScript surface (out of scope; CDP has no Space concept).

🤖 Generated with Claude Code

Muqsit and others added 4 commits August 29, 2026 23:24
…licate (PHNX-2399)

Arc is single-instance: relaunching the Arc binary with a fresh --user-data-dir
produced a stray window and no CDP endpoint rather than a debuggable instance —
the "second window/instance" bug. An Arc profile now ATTACHES to the Arc the user
already has open, and when that Arc exposes no CDP endpoint on the profile's port
it fails loud with the one relaunch that fixes it (open -a Arc --args
--remote-debugging-port=<port>) instead of silently spawning a duplicate.

- drivers/local.ts: for browser==='arc', never fall through to launchBrowser;
  attach or throw arcAttachRequiredError (names the profile, the port, the fix).
- service.ts: arcNotDrivableError now describes the truth — Arc DOES expose page
  targets and honors Page.navigate; only Target.createTarget (new-tab creation, a
  CDP-only op) crashes it, so tab-creating verbs fail clearly.
- browser.ts: --target-filter is accepted for --browser arc (bind the profile to
  a Space's tab), not just --electron.
- open-url.ts: Arc viewer message corrected — it can't open a fresh viewer tab.
- docs/browser.md + CHANGELOG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…99 review follow-up)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rst navigate (PHNX-2399 review)

Two blockers from review:
1. --target-filter was accepted/stored for Arc profiles but never consulted at
   drive time — pickReusableTargetWithoutCreate now scopes reusable tabs to the
   bound url:/title: filter (and refuses when nothing matches), so navigate drives
   the pinned Space rather than an unrelated tab.
2. The documented 'navigate --profile arc --url …' first-use flow threw on a
   task-less profile: start({url}) for Arc now routes through navigate() (like
   Electron) so it attaches to a reusable tab instead of createPageTarget's refusal;
   it still fails loud when there is genuinely no tab to drive.

Also corrects the browserType doc comment (Arc DOES expose page targets). Tests:
new Arc target-filter selection, no-match refusal, and first-use attach; full
browser suite green (567).
@muqsitnawaz

Copy link
Copy Markdown
Contributor Author

Non-author review (code-reviewer subagent — prix-cloud paused, #1767)

Verdict: APPROVE (clear to merge)

First pass found two BLOCKERS, both now fixed in bfb7275b1 and re-verified by re-tracing
the real runtime path (start()navigate()pickReusableTargetWithoutCreate()):

  • BLOCKER feat(rotate): replace rotate with balanced (weighted random) #1--target-filter was accepted/stored for Arc but never consulted at
    drive time
    (a lying capability). pickReusableTargetWithoutCreate (service.ts:778-816)
    now parses conn.targetFilter and scopes the reusable-tab set to the url:/title:
    substring, refusing (returns undefined) when nothing matches rather than borrowing an
    unrelated tab — the same contract pickWindowTarget already held for Electron. Covered by
    a bound url:target-filter selects the matching Space tab, never an unrelated one and
    refuses rather than borrowing an unrelated tab when the filter matches nothing
    (service.test.ts:1195-1233), built on realistic multi-tab CDP target lists.

  • BLOCKER feat(secrets): move bundle storage from YAML to Keychain (iCloud-syncable) #2 — the documented navigate --profile arc --url … first-use flow threw on a
    task-less profile. start()'s implicit-navigate branch (service.ts:669-684) now routes
    Arc through this.navigate(...) exactly like Electron, so the first call attaches to a
    reusable tab instead of hitting createPageTarget's refusal. The retained
    start with a url throws the clear error and never creates a target test still passes
    (empty target list → genuine fail-loud preserved, zero Target.createTarget), and the new
    start({url}) on a task-less Arc profile ATTACHES to a reusable tab reproduces the docs' step 3.

The stale browserType doc comment (earlier NICE) is corrected too. Required Tests / test
green; full src/lib/browser/ suite 567 passed. No remaining findings.

@muqsitnawaz
muqsitnawaz merged commit 47b66e8 into main Aug 30, 2026
9 checks passed
@muqsitnawaz
muqsitnawaz deleted the phnx-2399-arc-attach branch August 30, 2026 04:30
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