fix(hackbrowser): browser stability + subprocess lifecycle hardening - #85
Merged
Conversation
…nux-only - Add 6 Chromium flags to prevent renderer throttling/death during window drag/resize/minimize: --disable-backgrounding-occluded-windows, --disable-renderer-backgrounding, --disable-background-timer-throttling, --disable-hang-monitor, --disable-ipc-flooding-protection, --disable-component-update - Move --no-sandbox, --disable-setuid-sandbox, --disable-dev-shm-usage, --no-zygote to Linux-only (PLATFORM_ARGS) — these are Docker/Linux flags that can cause instability on macOS Closes part of #84
The browser could die mid-crawl (crash, disconnect, page close) with
no detection — the agent continued issuing commands to a dead browser,
wasting tokens and eventually throwing an unhandled error that lost all
captured data.
Changes:
- Add BrowserHealth tracker with browser.on('disconnected'),
page.on('close'), page.on('crash') event handlers
- Check isBrowserDead() at each BFS iteration boundary (both
single-cred and multi-cred paths)
- Wrap explorePageWithAI in try-catch — on browser death, break
the BFS loop instead of propagating the error
- Graceful shutdown: drain captured requests, skip panel events
and waitForTimeout on dead browser, report partial results
in errors[] instead of throwing
- browser.close() wrapped in .catch() to handle already-dead browser
Both run() and runMultiCredential() are covered.
Closes #84
…window resize viewport: null when headless=false lets the CSS viewport track the actual window dimensions. The overlay panel (position:fixed) now repositions naturally on resize instead of staying pinned to the 1920x1080 virtual viewport. --window-size=1920,1080 launch arg keeps the initial size.
Card was position:absolute inside a fixed root — when the viewport diverged from the window (Playwright viewport lock) the card drifted off-screen. Now position:fixed on the card itself, max-width clamp, and a resize listener that keeps it within visible bounds.
When the user hits stop (AbortSignal), skip browser.close() so the window stays open for manual inspection. Both run() and runMultiCredential() paths affected.
… killing
When user cancels the crawl (AbortSignal), the worker now waits for
browser.on('disconnected') — keeping the browser alive until the user
manually closes it. The worker process stays alive during this wait,
so the launcher's IPC reader loop blocks naturally until the browser
closes and the result is sent.
Only applies in headed mode — headless crawls still close immediately.
…roc.kill Replace proc.kill() with stdin.end() in backgroundRun's finally block. The worker process exits naturally when stdin closes, allowing Chrome to shut down gracefully instead of being force-killed mid-operation.
In headed mode, always wait for user to close the browser window instead of calling browser.close(). Prevents Chrome from closing when the stop-loading button cancels a navigation mid-crawl. Also wraps initial page.goto in try-catch so navigation failure keeps the browser open for manual interaction.
Add waitForBrowserClose() helper that handles three edge cases: - Race condition: re-checks isConnected() after attaching listener - Parent crash: races disconnect against abort signal - Chrome hang: 30-minute max timeout as backstop Also wraps runMultiCredential initial goto in try-catch (same pattern as run()) to prevent browser closing on navigation failure.
When parent closes stdin (shutdown or crash), abort the crawl controller so run()'s disconnect-wait resolves and the worker exits cleanly instead of keeping Chrome alive indefinitely.
Add 10s kill timeout after stdin.end() as a backstop if the worker hangs. On the crash detection path (worker exited without result), force-kill to clean up orphaned Chrome child processes.
Wrap crawl body in try-finally to guarantee clearInterval on any exit path (auth errors, navigation failures). Add second drain pass after csEmit to catch late captures. In headed mode, keep draining captures during manual browsing until user closes the browser.
If runWorker is still hanging 5s after stdin close (e.g. stuck Playwright operation that ignores abort signal), force-exit the worker process to prevent indefinite orphan.
Contributor
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
position: fixed+visualViewportresize listener +viewport: nullin headed modestdin.endinstead ofproc.kill), browser stays open in headed modeFiles Changed
packages/hackbrowser/src/agent.ts—waitForBrowserClose()helper, try-catch initial goto, post-crawl drainpackages/hackbrowser/src/stealth.ts— headed mode viewport + window-size argspackages/hackbrowser/src/panel/inject.ts— fixed overlay + resize listenerpackages/cyberstrike/src/tool/hackbrowser-launcher.ts— graceful shutdown + kill timeoutpackages/cyberstrike/src/hackbrowser-subprocess/hackbrowser-worker.ts— abort on stdin close + watchdogTest Plan
Closes #84