Skip to content

feat(browse): extend GSTACK_CHROMIUM_PATH support to headless launch#1614

Open
shohu wants to merge 1 commit into
garrytan:mainfrom
shohu:feat/cloak-browser-headless-support
Open

feat(browse): extend GSTACK_CHROMIUM_PATH support to headless launch#1614
shohu wants to merge 1 commit into
garrytan:mainfrom
shohu:feat/cloak-browser-headless-support

Conversation

@shohu
Copy link
Copy Markdown

@shohu shohu commented May 19, 2026

Summary

launchHeaded() already reads GSTACK_CHROMIUM_PATH to select a custom Chromium binary, but launch() (headless mode — the default $B goto path) ignores the env var and always falls back to Playwright's bundled binary.

This PR applies the same pattern to launch():

  • If GSTACK_CHROMIUM_PATH is set and the path exists (existsSync guard), it is passed as executablePath to chromium.launch()
  • If unset or the binary is missing, falls back to standard Playwright Chromium automatically — no breaking change
  • Logs [browse] Using custom Chromium: <path> on startup when active (matches the debug style used elsewhere)

Motivation

This enables drop-in use of stealth Chromium forks (e.g. CloakBrowser) for headless automation without needing to fork gstack or patch vendored files after every upgrade.

Before: GSTACK_CHROMIUM_PATH only worked for headed mode ($B connect). Headless $B goto always used Playwright's bundled binary regardless.

After: Setting GSTACK_CHROMIUM_PATH in the environment applies to both headless and headed mode consistently.

Usage

# ~/.zshrc
export GSTACK_CHROMIUM_PATH="$(python3 -c 'from cloakbrowser import binary_info; print(binary_info()["binary_path"])')"
# Verify: navigator.webdriver should be false with CloakBrowser
$B goto https://example.com
$B js 'navigator.webdriver'   # → false

Test plan

  • bun run build passes
  • $B goto with GSTACK_CHROMIUM_PATH set → navigator.webdriver: false, window.chrome: object
  • $B goto without GSTACK_CHROMIUM_PATH → falls back to standard Playwright Chromium (existsSync guard)
  • No changes to headed mode (launchHeaded) behaviour

🤖 Generated with Claude Code

launchHeaded() already reads GSTACK_CHROMIUM_PATH to select a custom
Chromium binary, but launch() (headless mode, the default $B goto path)
ignores the env var and always falls back to Playwright's bundled binary.

This change applies the same pattern to launch(): if GSTACK_CHROMIUM_PATH
is set and the path exists, pass it as executablePath to chromium.launch().
If unset or the binary is missing, falls back to standard Playwright
Chromium automatically (existsSync guard).

Motivation: enables drop-in use of stealth Chromium forks (e.g. CloakBrowser)
for headless automation without forking gstack or patching vendored files.
@shohu shohu force-pushed the feat/cloak-browser-headless-support branch from 8a119d8 to 16100e2 Compare May 19, 2026 23:48
@jbetala7
Copy link
Copy Markdown
Contributor

One correctness edge with the headless path: this applies GSTACK_CHROMIUM_PATH to launch(), but the extension branch above still unconditionally appends --disable-extensions-except / --load-extension whenever BROWSE_EXTENSIONS_DIR is set. launchHeaded() has an isCustomChromium() guard for this exact case because GBrowser / GStack Browser builds already bake the extension in, and loading it twice can hit the ServiceWorkerState::SetWorkerId DCHECK crash. With this PR, a user running headless/off-screen via BROWSE_EXTENSIONS_DIR plus GSTACK_CHROMIUM_PATH to a baked-extension Chromium can still take the double-load path.

Can this mirror launchHeaded() by skipping the extension flags when isCustomChromium() is true, with a regression around GSTACK_CHROMIUM_KIND=custom-extension-baked or a GBrowser path? That would keep the new headless custom-binary support aligned with the existing headed safety path.

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.

2 participants