[Claimed #1830] feat(cli): add --connect flag for daemon mode with existing Chrome#1831
[Claimed #1830] feat(cli): add --connect flag for daemon mode with existing Chrome#1831github-actions[bot] wants to merge 1 commit intomainfrom
Conversation
Adds a --connect <url> option that tells the browse daemon to attach to an existing Chrome instance via CDP WebSocket URL instead of launching its own Chrome. The daemon persists between commands (refs from snapshot are cached), but Chrome lifecycle is managed externally. Use case: remote node management where Chrome is launched with custom flags (anti-detection, profiles, specific ports) and the browse CLI needs to interact with it while preserving accessibility tree refs. --ws (stateless, no ref cache) remains unchanged. --connect (daemon mode, persistent refs, external Chrome) is new. Made-with: Cursor
|
Greptile SummaryAdds a
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User as CLI User
participant CLI as browse CLI
participant Daemon as Daemon Process
participant Chrome as External Chrome
Note over User, Chrome: --connect mode (persistent daemon, external Chrome)
User->>CLI: browse --connect [cdp-url] open [url]
CLI->>CLI: runCommand() checks opts.ws (not set)
CLI->>Daemon: ensureDaemon(session, headless, connectUrl)
Daemon->>Chrome: Connect via CDP WebSocket
Chrome-->>Daemon: CDP connection established
Daemon->>Daemon: Cache refs from accessibility tree
Daemon-->>CLI: Command result
CLI-->>User: Output
Note over User, Chrome: Subsequent command reuses daemon + cached refs
User->>CLI: browse --connect [cdp-url] click @0-5
CLI->>Daemon: sendCommand() (daemon already running)
Daemon->>Chrome: Execute click using cached ref
Chrome-->>Daemon: Result
Daemon-->>CLI: Result
CLI-->>User: Output
Note over User, Chrome: --ws mode (stateless, no daemon)
User->>CLI: browse --ws [cdp-url] open [url]
CLI->>Chrome: Direct Stagehand connection (no daemon)
Chrome-->>CLI: Result (no ref caching)
CLI-->>User: Output
|
There was a problem hiding this comment.
2 issues found across 1 file
Confidence score: 2/5
- There is a high-confidence, user-facing regression in
packages/cli/src/index.ts:ensureDaemoncan return early based only on mode and ignore a mismatchedconnectUrl, which can silently attach to the wrong daemon target. - The
startpath inpackages/cli/src/index.tsdoes not passopts.connectintoensureDaemon, sobrowse --connect <url> startmay launch a new Chrome instance instead of attaching as requested. - Given two concrete issues with severity 7-8/10 and clear behavioral impact in CLI attach/start flows, merge risk is elevated until these are fixed.
- Pay close attention to
packages/cli/src/index.ts- daemon reuse and--connectpropagation can misroute connections and break expected startup behavior.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/cli/src/index.ts">
<violation number="1" location="packages/cli/src/index.ts:1354">
P1: `ensureDaemon` returns early when the mode matches, but doesn't check whether the running daemon's `connectUrl` matches the requested one. A daemon started without `--connect` (or with a different URL) will be silently reused, sending commands to the wrong Chrome instance. The connect URL should be persisted and compared alongside mode.</violation>
<violation number="2" location="packages/cli/src/index.ts:1444">
P1: The `start` command doesn't forward `opts.connect` to `ensureDaemon`, so `browse --connect <url> start` will launch a new Chrome instead of attaching to the external one. Pass `opts.connect` like `runCommand` does.</violation>
</file>
Architecture diagram
sequenceDiagram
participant User as CLI User
participant CLI as CLI Client
participant Daemon as CLI Daemon Process
participant Core as Stagehand Core
participant Chrome as External Chrome (CDP)
User->>CLI: browse --connect <ws_url> <command>
CLI->>CLI: NEW: Parse --connect URL
alt If --ws provided (Legacy/Stateless)
CLI->>Core: Initialize Stagehand (Direct)
Core->>Chrome: Connect via CDP
Core-->>CLI: Return Result
else If --connect or default (Persistent)
CLI->>CLI: ensureDaemon(session, connectUrl)
opt Daemon Not Running
CLI->>Daemon: spawn(node index.js daemon --connect <url>)
Daemon->>Core: NEW: init with { cdpUrl: connectUrl }
Core->>Chrome: Attach to existing instance
Note over Daemon,Chrome: No browser launch performed
end
CLI->>Daemon: sendCommand(command, args)
Daemon->>Core: executeCommand()
Core->>Chrome: Interaction (e.g., snapshot)
Chrome-->>Core: Accessibility Tree / Refs
Core->>Core: Cache Refs in Daemon Memory
Core-->>Daemon: Command Result
Daemon-->>CLI: Response
end
CLI-->>User: Output Result
Note over CLI,Daemon: Error Handling / Retry Flow
alt Command Fails (Retry Logic)
CLI->>CLI: sendCommand (Attempt 2)
CLI->>Daemon: ensureDaemon()
opt If Max Retries Reached
alt NEW: Using --connect
CLI->>CLI: Skip killChromeProcesses()
else Local Mode
CLI->>CLI: killChromeProcesses()
end
CLI->>Daemon: Full Restart
end
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -288,7 +288,7 @@ interface DaemonResponse { | |||
| // Default viewport matching Stagehand core | |||
There was a problem hiding this comment.
P1: The start command doesn't forward opts.connect to ensureDaemon, so browse --connect <url> start will launch a new Chrome instead of attaching to the external one. Pass opts.connect like runCommand does.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/index.ts, line 1444:
<comment>The `start` command doesn't forward `opts.connect` to `ensureDaemon`, so `browse --connect <url> start` will launch a new Chrome instead of attaching to the external one. Pass `opts.connect` like `runCommand` does.</comment>
<file context>
@@ -1432,6 +1441,7 @@ async function ensureDaemon(session: string, headless: boolean): Promise<void> {
interface GlobalOpts {
ws?: string;
+ connect?: string;
headless?: boolean;
headed?: boolean;
</file context>
| } | ||
|
|
||
| async function ensureDaemon(session: string, headless: boolean): Promise<void> { | ||
| async function ensureDaemon(session: string, headless: boolean, connectUrl?: string): Promise<void> { |
There was a problem hiding this comment.
P1: ensureDaemon returns early when the mode matches, but doesn't check whether the running daemon's connectUrl matches the requested one. A daemon started without --connect (or with a different URL) will be silently reused, sending commands to the wrong Chrome instance. The connect URL should be persisted and compared alongside mode.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/index.ts, line 1354:
<comment>`ensureDaemon` returns early when the mode matches, but doesn't check whether the running daemon's `connectUrl` matches the requested one. A daemon started without `--connect` (or with a different URL) will be silently reused, sending commands to the wrong Chrome instance. The connect URL should be persisted and compared alongside mode.</comment>
<file context>
@@ -1344,7 +1351,7 @@ async function stopDaemonAndCleanup(session: string): Promise<void> {
}
-async function ensureDaemon(session: string, headless: boolean): Promise<void> {
+async function ensureDaemon(session: string, headless: boolean, connectUrl?: string): Promise<void> {
const wantMode = await getDesiredMode(session);
assertModeSupported(wantMode);
</file context>
|
This mirrored PR could not be refreshed automatically after approval by @pirate. Refresh reason: |
Mirrored from external contributor PR #1830 after approval by @miguelg719.
Original author: @peytoncasper
Original PR: #1830
Approved source head SHA:
09f21611c547a9da5cf1efe6b7f41e0170079587@peytoncasper, please continue any follow-up discussion on this mirrored PR. When the external PR gets new commits, this same internal PR will be marked stale until the latest external commit is approved and refreshed here.
Original description
Summary
--connect <url>CLI option that starts the daemon attached to an existing Chrome instance via CDP WebSocket URL--ws(stateless, no ref caching),--connectgives you persistent daemon mode without launching ChromeUse case
Remote node management where Chrome is launched externally with custom flags (anti-bot detection, persistent profiles, specific ports) and the browse CLI needs to interact with it while preserving refs between commands.
Example:
Changes
packages/cli/src/index.ts: Added--connectoption toGlobalOpts, program options,runDaemon,ensureDaemon,sendCommand, andrunCommand--connectis set,ensureBrowserInitializeduseslocalBrowserLaunchOptions: { cdpUrl: connectUrl }instead of launching ChromesendCommandskipskillChromeProcesseswhen using--connect(Chrome is externally managed)Test plan
browse --connect <ws_url> open <url>navigates successfullybrowse --connect <ws_url> snapshot -creturns accessibility tree with refsbrowse --connect <ws_url> click @0-1uses cached refs from previous snapshot--wsbehavior unchangedMade with Cursor
Summary by cubic
Add a --connect flag to run the CLI daemon against an existing Chrome via CDP, enabling persistent ref caching without launching Chrome. Existing --ws behavior remains stateless and unchanged.
--connect <url>attaches the daemon to external Chrome; refs persist across commands.localBrowserLaunchOptions.cdpUrlwhen provided, passes the URL through daemon startup/retries, and skips killing Chrome during retry logic.Written for commit 09f2161. Summary will update on new commits. Review in cubic