Skip to content

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

Open
shohu wants to merge 2 commits 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 2 commits 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.

When BROWSE_EXTENSIONS_DIR is set alongside GSTACK_CHROMIUM_PATH pointing
at a baked-extension build (GBrowser / GStack Browser), the headless launch()
path was unconditionally adding --disable-extensions-except / --load-extension.
This causes the same ServiceWorkerState::SetWorkerId DCHECK crash that
launchHeaded() already guards against via isCustomChromium().

Mirror the existing guard: skip --load-extension flags when isCustomChromium()
returns true; always push the off-screen window geometry args.
@shohu
Copy link
Copy Markdown
Author

shohu commented May 20, 2026

Good catch — fixed in the latest commit (e2de3b3).

The launch() headless path now mirrors launchHeaded() exactly: it checks isCustomChromium() before pushing --disable-extensions-except / --load-extension, so a user running with BROWSE_EXTENSIONS_DIR + a baked-extension GSTACK_CHROMIUM_PATH won't hit the double-load DCHECK crash.

The off-screen window geometry args (--window-position / --window-size) are still pushed unconditionally since those are harmless with any binary.

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