Skip to content

fix(ego-linux): recognize the managed browser by the switches Chrome uses - #65

Open
zhuran24 wants to merge 1 commit into
opencue:mainfrom
zhuran24:fix/linux-duplicate-switches
Open

zhuran24 wants to merge 1 commit into
opencue:mainfrom
zhuran24:fix/linux-duplicate-switches

Conversation

@zhuran24

Copy link
Copy Markdown

Summary

src/chrome.mjs decides which running process is the managed browser by reading its switches. It read the first occurrence of a switch where Chromium uses the last, and it found switches inside other arguments' values. A launcher that prepends flags therefore made ego disown its own browser, then refuse to start a new one or replace the running one.

Related issue

None: issues are disabled on this repository.

Changes

The last occurrence of a switch wins

Launcher wrappers can put the user's own flags before the arguments ego passes. The google-chrome-stable wrapper in Arch Linux's google-chrome package, for example, inserts the contents of $XDG_CONFIG_HOME/chrome-flags.conf ahead of "$@". The managed browser's command line then carries a switch twice. Chromium keeps the last value (InsertOrAssign in CommandLine::AppendSwitchNative, base/command_line.cc); processFlagValue() returned the first. As a result:

  • A second --user-data-dir: browserStatus() reported the managed browser as not running, and the next ensureBrowser() found no reusable browser, tried to launch one and refused, because the profile's lock owner, its own browser, "is not a managed Ego Lite browser".
  • A second --remote-debugging-port: browserStatus() reported the browser as not running; ensureBrowser() without a browser.json (for example under another state root) killed it and launched a new one; stopBrowser() could not close it over CDP and signalled it instead.
  • unsafeBrowserLaunchFlags() judged --remote-debugging-address=127.0.0.1 … --remote-debugging-address=0.0.0.0 safe by its first value, although Chrome listens on 0.0.0.0.

processFlagValues() now returns every occurrence. Ownership checks use the last one, and the unsafe-launch check rejects the browser when any value is not a loopback address. Like Chromium, which stops reading switches at a bare -- (AppendSwitchesAndArguments), nothing after -- counts.

Flag text inside another argument is not a flag

For a tokenized argv, processFlagValue() and processHasArg() also searched the space-joined arguments. An argument such as --note=x --user-data-dir=<profile> or --note=x --class=ego-lite-linux thus counted as the switch it contains. Joined-text scanning now runs only on the single-string title that Chromium writes into /proc/<pid>/cmdline, where argument boundaries are already lost, and there a flag has to start a word. SetProcessTitleFromCommandLine() (base/process/set_process_title.cc) builds that title by joining argv with spaces and no quoting, so quote characters are treated as part of a value: a profile path containing an apostrophe still matches.

Reproduction

With a real headless Chrome started through a wrapper that prepends its own switches, and EGO_LINUX_CHROME pointing at the wrapper, the state and profile in a scratch directory:

#!/bin/sh
exec /opt/google/chrome/google-chrome --user-data-dir=/tmp/elsewhere --remote-debugging-port=9 "$@"

/tmp/elsewhere exists; it stands in for the user's normal profile. Calling ensureBrowser({ headless: true }), then browserStatus(), then ensureBrowser() again:

Step main This branch
first ensureBrowser() launches launches
browserStatus() running: false running: true
second ensureBrowser() throws Profile lock owner <pid> is not a managed Ego Lite browser; refusing to edit or unlock <profile> reuses the same pid (launched: false)

With a wrapper that prepends only --remote-debugging-port=9, deleting browser.json after the first launch:

Step main This branch
browserStatus() running: false running: true
ensureBrowser() kills the running browser and launches another reuses the same pid
stopBrowser() signals the browser; the profile records exit_type: SessionEnded closes it over CDP; exit_type: Normal

Verification

npm test in package/ego-linux, run in a clean environment as in CI: no inherited EGO_* variables, and the XDG directories in a scratch directory.

Check main This branch
npm test 276 passed 283 passed (twice)
test/process-flags.test.mjs (7 new tests) against main's chrome.mjs 6 fail, 1 passes¹ 7 pass
The new file 6 times while another process calls reapOrphanedBrowsers() every 20 ms n/a 6/6 pass
prettier --check on the changed files n/a clean

¹ switches after a bare -- do not count passes on main, which reads the first occurrence; it guards the last-occurrence rule.

The new tests use stand-in processes and a local HTTP endpoint, not a real browser. Each stand-in's command line is read back before any assertion, so a failed process query cannot make a negative test pass. Their profile directories exist, so a parallel suite's machine-wide reapOrphanedBrowsers() leaves them alone. On Windows, where setting process.title does not change the command line, the rewritten-title test is skipped and the -- test checks only the tokenized form; Windows argv comes from splitCommandLine(). The tests have not been run on Windows locally.

Impact

No public helper change. This affects which running browser ego-linux treats as its own when it reuses, reports on, stops or reaps a browser.

🤖 Generated with Claude Code

…uses

processFlagValue() returned the first occurrence of a switch, but
Chromium keeps the last. Launcher wrappers can put the user's own flags
before the arguments ego passes; Arch's google-chrome-stable, for one,
inserts ~/.config/chrome-flags.conf ahead of "$@".

- A second --user-data-dir made the managed browser stop matching its
  profile: browserStatus() reported it not running, and the next
  ensureBrowser() refused to launch because the profile lock owner "is
  not a managed Ego Lite browser".
- A second --remote-debugging-port made browserStatus() report it not
  running, made ensureBrowser() without a browser.json kill it and launch
  another, and made stopBrowser() signal it instead of closing it over
  CDP.
- unsafeBrowserLaunchFlags() judged a repeated --remote-debugging-address
  by its first value; any non-loopback value now makes the browser
  unsafe.

processFlagValue() and processHasArg() also searched the space-joined
argv when argv was tokenized, so "--note=x --user-data-dir=<profile>" or
a --class marker inside another argument's value counted as the switch.
That scan now runs only on the single-string title Chromium writes into
/proc/<pid>/cmdline, where a flag has to start a word. Chromium joins
that title without quoting, so quote characters stay part of a value and
a profile path with an apostrophe still matches. As in Chromium, no
switch counts after a bare "--".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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