fix(browser): swallow waitForEvent('download') timeout so failed clicks don't crash the host#40
Draft
caffeinum wants to merge 1 commit into
Draft
Conversation
Most clicks don't trigger a file download. The unawaited
page.waitForEvent('download', { timeout: 5000 }) rejects on timeout,
escaping as an unhandledRejection that kills the whole watcher
process. Attach a no-op .catch() so the timeout is treated as
"no download, proceed" — matching the existing perform_click
behavior at the second download-wait call site (parity, not a new
heuristic).
Repro: any SaaS dashboard with nested Stripe iframes (e.g.
browser-use.com/settings) where a side-nav click is heuristically
flagged as download-capable but is actually a route nav. Crash
visible in queue logs as
"unhandledRejection: page.waitForEvent: Timeout 5000ms exceeded".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
In
BrowserSession._click_element_node, when a downloads dir is configured we armpage.waitForEvent('download', { timeout: 5000 })BEFORE callingperformClick(), then await it after. IfperformClick()throws (e.g. element-not-clickable, click intercepted by overlay, nested iframe issues), the await never executes and the waitForEvent promise rejects 5s later as an unhandledRejection — which in a long-running Node host (the queue watcher, in our case) escapes upward and crashes the process.Repro
Any SaaS dashboard with nested Stripe iframes — e.g.
browser-use.com/settings. A click on a side-nav element is heuristically flagged as download-capable but is actually a route nav. The click throws (Playwright can't reach the inner element), the dangling 5s timer fires, and the host dies withunhandledRejection: page.waitForEvent: Timeout 5000ms exceeded.Fix
One line:
downloadPromise.catch(() => null);immediately after creation. This marks the rejection as observed without changing semantics — the realawait this._withAbort(downloadPromise, signal)below still consumes the outcome and the existing try/catch still handles the timeout-as-no-download case. Matches the parity behavior at the second download-wait site inperform_click.Reference: Python upstream
Python
browser-usedoesn't have this bug — it uses CDP-driven download detection inbrowser_use/browser/watchdogs/default_action_watchdog.py::_execute_click_with_download_detection. The pattern is:asyncio.Event.wait()is created after the click resolves, so a click failure can't leave a dangling awaitable. The TS port translated this into a PlaywrightwaitForEventpattern that arms the future before the click — introducing the regression. This PR is the minimum patch to make the TS pattern safe; a deeper refactor to mirror python's CDP-driven approach is out of scope.Test plan
test/browser-session.test.tscase that mocks a failing click + verifies no unhandledRejection escapes🤖 Generated with Claude Code