fix(downloads): prevent duplicate local downloads via single-dispatch… #2903
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.
…; disable local JS fetch
Summary: Ensures exactly one file is written and one FileDownloadedEvent emitted for a single user download in local sessions. Remote behavior unchanged.
Root Cause: Multiple dispatch paths and dual file writes (native + JS fetch) for the same download in browser_use/browser/watchdogs/downloads_watchdog.py. Polling fallback also appended directly to the session list.
Changes:
De‑dup by GUID: add a per‑download handled flag to avoid multiple FileDownloadedEvent emissions across progress/JS/polling.
Disable local JS fetch by default via a private guard so the browser’s native download is the single writer.
Centralize tracking via events: remove direct _downloaded_files append in polling; rely on BrowserSession.on_FileDownloadedEvent.
File changed: browser_use/browser/watchdogs/downloads_watchdog.py
Behavior Changes:
Local: one file on disk (no “(1)” suffix) and one FileDownloadedEvent.
Remote: unchanged.
How To Test:
Option A (example script): set GOOGLE_API_KEY, run python examples/features/download_file.py. Expect one file in downloads, single FileDownloadedEvent, and one unique entry in browser_session.downloaded_files.
Option B (clean home): macOS/Linux:
TMP_HOME=$(mktemp -d); mkdir -p "$TMP_HOME/Downloads"
HOME="$TMP_HOME" GOOGLE_API_KEY=YOUR_KEY python examples/features/download_file.py
ls -1 "$TMP_HOME/Downloads" → exactly one file, no “(1)”.
Open Questions:
Would you like an opt‑in BrowserProfile flag to re‑enable the local JS fetch path (default off) for special cases?
Are there environments where local Browser.downloadProgress often lacks filePath, requiring a longer polling fallback? If so, what timeout would you prefer?
Changelog: Fix duplicate downloads in local sessions by single-dispatch and preferring native download.
Fix status: Implemented in b14fef2.
Move CDP handlers to method scope (not inside try).
Keep tab-based indentation; align outer except.
Single-dispatch: guard duplicates via GUID handled flag; remove direct session mutation in polling; disable local JS fetch path by default.
ruff-format adjustments committed; lint + type-check pass locally.
CI note: The remaining failure is tests/ci/test_radio_buttons.py due to missing LLM API key in fork CI (401 from OpenAI). The test invokes an LLM and needs repo secrets, which aren’t exposed to forks. The downloads fix itself passes lint/format/type-check.
Request: Please re-run on maintainer CI (with secrets) or let me know if you prefer I add a small skip guard so this test skips when API keys are absent.
Summary by cubic
Prevents duplicate local downloads by enforcing single-dispatch and using the native browser download as the only writer. Local sessions now create one file and emit one FileDownloadedEvent; remote behavior is unchanged.