Skip to content

chrome-devtools-cli v2.1.0

Choose a tag to compare

@aeroxy aeroxy released this 29 Sep 15:14
· 8 commits to main since this release

1. High-Level Summary (TL;DR)

  • Impact: Medium. The CLI can now connect when it is not allowed to read the browser's profile directory, adds --port / CHROME_PORT for connecting to a local DevTools server by port, and reports its real version again.
  • Key Changes:
    • ✨ Connect by Port: New --port <n> flag and CHROME_PORT env var. Works with both the chrome://inspect/#remote-debugging server and a browser launched with --remote-debugging-port; the CLI tells them apart on its own.
    • 🛟 Unreadable-Profile Fallback: When the default profile's DevToolsActivePort exists but cannot be read, the CLI connects to the chrome://inspect server on port 9222 with a warning, instead of failing.
    • 📝 Accurate Errors: Read failures include the OS error, and a permission error names the real cause (macOS privacy approval, security software, file permissions) instead of telling you to enable remote debugging.
    • 🔒 Hardened Probe: The port probe caps its reply at 64 KiB and only accepts an endpoint on the probed local port.
    • 🔢 Correct Version: --version reports 2.1.0. The 2.0.0 build reported 1.6.0.

2. Visual Overview (Code & Logic Map)

graph TD
    %% Business Goals
    BG1["Connect When the Profile Is Unreadable"]:::goal
    BG2["Connect by Port to Either Server Kind"]:::goal
    BG3["Never Attach to the Wrong Endpoint"]:::goal
    BG4["Errors That Name the Real Cause"]:::goal

    %% Files/Modules
    subgraph "src/lib.rs (CLI)"
        L1["Cli.port (--port / CHROME_PORT)"]
    end

    subgraph "src/browser.rs (Resolution)"
        B1["resolve_ws_url()"]
        B2["read_devtools_active_port()"]
        B3["port_endpoint()"]
        B4["probe_port()"]
        B5["read_version_response()"]
        B6["inspect_ws_url()"]
    end

    BG1 <--> B2
    BG2 <--> B3
    BG2 <--> B4
    BG3 <--> B3
    BG3 <--> B5
    BG4 <--> B2

    L1 --> B1
    B1 --> B3
    B1 --> B2
    B3 --> B4
    B2 --> B4
    B4 --> B5
    B3 --> B6
    B2 --> B6

    classDef goal fill:#e3f2fd,color:#0d47a1,stroke:#0d47a1,stroke-width:2px;

3. Detailed Change Analysis

🔌 Connecting by Port

  • Component: Browser Resolution (src/browser.rs, src/lib.rs)
  • What Changed: resolve_ws_url checks for a port after --ws-endpoint and before profile discovery. A port names the server directly, so like --ws-endpoint it skips the --browser / --channel profile lookup.
  • New Configuration Flags:
    Flag / Env Var Default Description
    --port / CHROME_PORT (unset) Port of a local DevTools server. Skips reading DevToolsActivePort. Accepts 1 to 65535; cannot be combined with --ws-endpoint.
  • Server Detection: One GET /json/version to 127.0.0.1:<port> picks the URL:
    Reply Server Connects to
    200 + JSON Launched with --remote-debugging-port The advertised webSocketDebuggerUrl, which carries the session UUID that server requires
    404 The chrome://inspect/#remote-debugging toggle ws://127.0.0.1:<port>/devtools/browser, which that server accepts without the UUID (verified on Chrome 154)
    Connection refused Nothing An error naming the port and where to find the right one
  • Safety: Whatever holds the port writes the reply, so the reply is capped at 64 KiB, headers included, and an advertised endpoint must start with ws://127.0.0.1:<port>/. Anything else is refused rather than followed to another host, port or scheme.
  • Note: The probe is a raw loopback request rather than an HTTP client library, so proxy environment variables cannot reroute it. Chrome keeps the connection open even when asked to close it, so the reply is read by Content-Length.

🛟 Unreadable Profile Fallback

  • Component: Auto-connect (src/browser.rs)
  • What Changed: read_devtools_active_port now tells "permission denied" apart from "missing". A missing file keeps the enable-remote-debugging steps. A permission error on the default profile probes port 9222 and, when the chrome://inspect server answers, connects there with a warning on stderr:
    Warning: cannot read .../DevToolsActivePort: Operation not permitted (os error 1). Connecting to port 9222 instead; pass --port or set CHROME_PORT to connect by port without this warning.
    
  • Guards:
    • Only the default profile falls back. An explicit --user-data-dir usually names a separate instance, so an unreadable one is an error that points at --port.
    • Only a chrome://inspect server is accepted on 9222. Chrome 136+ ignores --remote-debugging-port on the default profile, so a server launched that way is another instance. It is refused, with a pointer to --port 9222 if it is the one you want.
    • When the fallback cannot be used, the error keeps the full explanation and adds what was found on 9222.
  • Why a Profile Can Be Unreadable: On recent macOS, the app running the CLI (your terminal or IDE) needs approval before it can access another app's data. Until that privacy prompt is approved, every read of Chrome's profile directory fails with "Operation not permitted". Security software and file permissions can do the same.

🗣️ Diagnostics

  • Component: Auto-connect Errors (src/browser.rs)
  • What Changed: Every DevToolsActivePort read error now includes the OS error, which was previously dropped. The permission-denied error explains the likely cause and how to connect by port instead. --port errors name the port and point at chrome://inspect/#remote-debugging (edge:// under --browser edge) or the port the browser was launched with.

4. Fixes

  • 🐛 A blocked profile was reported as "remote debugging is off". When the OS refused to let the CLI read DevToolsActivePort, the CLI dropped the OS error and printed the enable-remote-debugging steps, although the file was there and debugging was already on.
  • 🐛 --version reported 1.6.0 in the 2.0.0 release. The version files were not bumped when 2.0.0 was tagged. 2.1.0 reports its own version.

5. Impact & Risk Assessment

⚠️ Breaking Changes

  • None. No flag or command was removed or renamed.

📋 Upgrade Notes

  • Check for an existing CHROME_PORT. The CLI now reads it. If another tool already sets it, commands connect by that port instead of reading DevToolsActivePort, and they fail outright when CHROME_WS_ENDPOINT is set as well.
  • A blocked default profile now connects instead of failing, through port 9222, with a warning on every command. Set CHROME_PORT (or pass --port) to connect by port directly and silence the warning, or approve the macOS privacy prompt so the profile can be read again.
  • Coming from 1.6.0 or earlier? Read the 2.0.0 release notes as well: its kill-daemon changes are breaking.

🔍 Known Limitations

  • The fallback cannot tell Chrome from Edge. Whichever browser's chrome://inspect server holds port 9222 is the one it reaches, so under --browser edge it may attach to Chrome. The warning names the port; pass --port with the right one.
  • A browser restart costs one failed command over a chrome://inspect port connection. That URL has no session UUID, so the old daemon is reused once, fails with a WebSocket error and exits. The next command connects afresh and needs approval again.
  • The chrome://inspect side of --port relies on observed behavior: that server accepting /devtools/browser without the session UUID, as Chrome 154 does.