Skip to content

chrome-ws start silently fails on headless Linux as root; misleading error + CHROME_EXTRA_ARGS not honored in CLI path #35

@roybahat

Description

@roybahat

Summary

On a headless Linux box running as root, ./chrome-ws start fails silently: Chrome spawns, immediately dies, and the script reports a misleading "Chrome started but remote debugging not accessible" — when in fact Chrome was never able to start at all. The user has no supported way to pass --no-sandbox / --headless=new to the CLI's start path, because the CLI's start path does not honor CHROME_EXTRA_ARGS even though the MCP-mode launcher in this same repo does.

Environment

  • Digital Ocean Linux droplet, Ubuntu, running as root
  • /usr/bin/google-chrome → Chrome 147.0.7727.137
  • Node v22.22.2
  • superpowers-chrome cloned from main at HEAD on 2026-05-28
  • No prior Chrome processes; clean /tmp/chrome-debug

Repro

cd skills/browsing
./chrome-ws start
# (also tried with CHROME_EXTRA_ARGS set — no effect)
CHROME_EXTRA_ARGS="--no-sandbox --headless=new --disable-dev-shm-usage" ./chrome-ws start

Both produce:

Starting Chrome: /usr/bin/google-chrome
Chrome started but remote debugging not accessible
Try: curl http://127.0.0.1:9222/json/version

After either invocation:

ps -ef | grep '[c]hrome'            # nothing — Chrome is gone
ss -tln | grep 9222                 # no listener
curl http://127.0.0.1:9222/json/version
# curl: (7) Failed to connect to 127.0.0.1 port 9222

Running Chrome directly on the same box with the right flags works fine — proving Chrome itself is healthy and the problem is in how the CLI launches it:

google-chrome --no-sandbox --headless=new --disable-gpu \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome-test-profile about:blank &
sleep 2 && curl -s http://127.0.0.1:9222/json/version
# {"Browser":"Chrome/147.0.7727.137", ...}

What was hard about diagnosing this

  1. The error message is misleading. "Chrome started but remote debugging not accessible" implies Chrome is alive and the debug port is the issue (maybe firewalled, wrong host, etc.). In reality Chrome was never alive past a few ms. Spending time on the port is a dead end; the real failure mode is process exited immediately.

  2. The Chrome child's stderr is silently discarded. chrome-ws spawns Chrome with { detached: true, stdio: 'ignore' } (skills/browsing/chrome-ws ~line 335). So even when Chrome prints something like [FATAL] Running as root without --no-sandbox is not supported., the user never sees it. The only signal back is a curl failure 2 seconds later, which is a much more ambiguous symptom.

  3. CHROME_EXTRA_ARGS looks supported but isn't (for the CLI start path). The README documents CHROME_EXTRA_ARGS as the way to pass extra flags. lib/chrome-launcher-helpers.js reads it (~line 290) and merges it into the args. But skills/browsing/chrome-ws's start command builds its chromeArgs array inline (~line 329) and only adds --remote-debugging-port and --user-data-dir — it never consults CHROME_EXTRA_ARGS, never adds --headless, never adds --no-sandbox. So setting the env var as documented appears to do nothing, which is hard to tell apart from "I set it wrong."

  4. There's no obvious indication the two launch paths exist. Once I found the inconsistency between lib/chrome-launcher-helpers.js and skills/browsing/chrome-ws, the fix was obvious — but discovering the inconsistency required reading the source. From a user's vantage, "the README says CHROME_EXTRA_ARGS" + "setting CHROME_EXTRA_ARGS does nothing" reads like a bug in my shell, not a code path divergence.

What I think the right fix is

Primary: make chrome-ws's start command honor CHROME_EXTRA_ARGS exactly like lib/chrome-launcher-helpers.js does. Five-line change around line 332 of skills/browsing/chrome-ws:

   const chromeArgs = [
     `--remote-debugging-port=${effectivePort}`,
     `--user-data-dir=${userDataDir}`
   ];
+
+  // Honor CHROME_EXTRA_ARGS (matches lib/chrome-launcher-helpers.js used by MCP mode).
+  // Needed on headless Linux / running-as-root for --no-sandbox / --headless=new.
+  if (process.env.CHROME_EXTRA_ARGS) {
+    chromeArgs.push(...process.env.CHROME_EXTRA_ARGS.split(/\s+/).filter(Boolean));
+  }

That single change is what unblocked me — with CHROME_EXTRA_ARGS="--no-sandbox --headless=new --disable-dev-shm-usage --disable-gpu" the CLI launch succeeds and every other chrome-ws command works against it.

Secondary, more valuable long-term: improve the failure path so the user gets a real signal when Chrome dies. Two small changes that together would have turned this from "spent an hour reading source" into "got a one-line error":

  • Don't discard Chrome's stderr. Pipe Chrome's stderr to a tempfile or to the script's own stderr, at least until the debug port comes up. Then the user sees Running as root without --no-sandbox is not supported. (or whatever Chrome chose to say) instead of a 2-second timeout. Same fix would help every future "Chrome can't start" cause — missing shared libs, profile lock conflicts, etc., not just the as-root case.
  • Race Chrome's exit against the port poll. Today the script setTimeout(2000) then curls /json/version. If the child exits before that timeout, surface Chrome exited with code N before opening debug port rather than the current message. With detached: true you can still chrome.on('exit', ...) before unref(). Knowing Chrome exited (vs is running but unreachable) cuts the diagnostic search space in half.

Optional polish: in the README's "Linux/WSL2 tip" callout, add a one-liner: "Running as root or in some containers? You'll need CHROME_EXTRA_ARGS=\"--no-sandbox --headless=new --disable-dev-shm-usage\"." That makes the docs match what lib/chrome-launcher-helpers.js already supports and what chrome-ws would after the primary fix.

Happy to PR the primary fix if useful — the stderr-surfacing change is a bigger design call (where do the logs go? rotate them? truncate? log-on-failure-only?) that I'd rather leave to you.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions