Skip to content

LOC-7472 Look for BrowserStackLocal.exe on Windows before downloading - #187

Open
pranay-v29 wants to merge 2 commits into
masterfrom
sdk-7713-reuse-running-binary
Open

pranay-v29 wants to merge 2 commits into
masterfrom
sdk-7713-reuse-running-binary

Conversation

@pranay-v29

@pranay-v29 pranay-v29 commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #185 (LOC-7420). Customer ticket: SDK-7713.

Problem

On Windows, this library re-downloads the binary on every start and never reuses the existing one.

binaryPath() chooses the file to look for with this.windows:

var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal';

but this.windows was only set inside getBinaryFilename(), which runs on the download path. Local creates a fresh LocalBinary for each start, so when binaryPath() ran the flag was still undefined. On Windows it looked for BrowserStackLocal without the extension, never found it, and downloaded, overwriting BrowserStackLocal.exe.

Usually that just costs a full download on every run. When another run is executing the binary, the overwrite fails with EBUSY.

Evidence from the SDK-7713 logs

The customer's sdk-cli-debug.log shows the second run did not race the first. It started well after the first run's tunnel was up:

18:14:49  run 1 starts Local
18:15:17  run 1's Local is up          (28s: that is the per-run download)
18:15:31  run 2 starts Local           (binary already present and running)
18:15:31  run 2: EBUSY ... open '...\BrowserStackLocal.exe'   (153ms later)

Run 2 went straight to the download while BrowserStackLocal.exe was right there. On 1.5.13 that crashed the SDK CLI. On 1.5.15 (#185) it no longer crashes, but run 2 still cannot write the file, so it exhausts its retries and never gets a tunnel.

Change

Set this.windows in the constructor, the way Local.js:18 already does. binaryPath() then finds the existing .exe and reuses it, so there is no download and nothing to collide with.

Two lines. getBinaryFilename() still sets the flag too, so nothing that relied on that is affected.

This PR previously added a --version probe to reuse a busy binary after the collision. That is removed: with the correct filename the collision doesn't happen, and the probe was also what tripped the Semgrep detect-child-process rule.

Tests

Two tests in test/local_binary_busy_download.js. Each builds LocalBinary as on Windows (stubbing process.platform), places a real BrowserStackLocal.exe in its directory, and fails if a download is attempted:

✔ reuses BrowserStackLocal.exe instead of re-downloading (sync)
✔ reuses BrowserStackLocal.exe instead of re-downloading (async)

Both fail against 1e479a6 (1.5.15) with should not re-download, and pass with this change. 15/15 across local_binary_busy_download.js and local_start_output_handling.js. The Binary filename suite in test/local.js, which sets hostOS after construction, still passes 5/5. ESLint clean.

Impact beyond SDK-7713

  • Every Windows user of this package, including the SDK CLI (browserstack-binary) for every SDK language, currently downloads the binary on every run. This removes that.
  • It likely feeds LOC-7420 too: that customer's log showed Downloading in sync on each run, and a freshly written .exe on each run is exactly what antivirus scans and locks.

Not in scope

  • No cross-process lock on ~/.browserstack. Two runs starting on a machine with no binary can still both download at once. That case is now much rarer, since it only happens when the binary is genuinely absent.
  • execFile in Local.start still throws spawn failures synchronously. The SDK CLI uses start(), so an unrunnable binary could still crash the CLI there. Wants its own change.

Delivery

Once released, browserstack-binary needs to bump from ^1.5.15 so the SDK CLI picks it up, as SDK-7712 did for 1.5.15.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Windows behavior so existing BrowserStack Local binaries are reused by both synchronous and asynchronous calls instead of triggering another download.

Parallel runs on one machine share ~/.browserstack/BrowserStackLocal.exe.
While one run is executing it, Windows keeps the file locked for that
run's whole lifetime, so a second run that reaches the download path hits
EBUSY on the open. 1.5.15 no longer crashes on that, but it waits three
seconds and then tries to delete and re-download the file -- which cannot
succeed while the first run holds it, so the second run exhausts its
retries and never gets a tunnel (SDK-7713).

A locked binary that still answers --version is a working binary. Before
waiting on or replacing it, probe it and reuse it if it runs; probe again
after the wait, since an AV scan or a finishing writer can release it.
A file that does not run is still replaced as before. The version check
mirrors browserstack-local-python's __verify_binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited), Workspace UI (inherited)

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1140d520-38e6-411c-916f-14bdbb03cb27

📥 Commits

Reviewing files that changed from the base of the PR and between 1e479a6 and cfe59ce.

📒 Files selected for processing (2)
  • lib/LocalBinary.js
  • test/local_binary_busy_download.js

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

📜 Recent review details
🧰 Additional context used
🪛 ast-grep (0.45.3)
test/local_binary_busy_download.js

[warning] 154-154: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(exe, 'binary', { mode: 0o755 })
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

🪛 ESLint
test/local_binary_busy_download.js

[error] 136-136: 'describe' is not defined.

(no-undef)


[error] 152-152: 'beforeEach' is not defined.

(no-undef)


[error] 158-158: 'afterEach' is not defined.

(no-undef)


[error] 163-163: 'it' is not defined.

(no-undef)


[error] 167-167: 'it' is not defined.

(no-undef)

🔇 Additional comments (2)
lib/LocalBinary.js (1)

20-21: LGTM!

test/local_binary_busy_download.js (1)

133-174: LGTM!


📝 Walkthrough

Walkthrough

LocalBinary now sets its Windows flag during construction. New tests check that synchronous and asynchronous calls to binaryPath() reuse an existing Windows binary without downloading it.

Changes

Windows binary detection

Layer / File(s) Summary
Set the platform flag and verify binary reuse
lib/LocalBinary.js, test/local_binary_busy_download.js
The constructor sets the Windows flag from process.platform. Tests check that synchronous and asynchronous calls return an existing BrowserStackLocal.exe path without attempting a download.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to cfe59

On Windows, an existing BrowserStackLocal.exe is now reused instead of being downloaded again, which avoids the EBUSY failure. Tests cover both the sync and async paths. No merge-blocking risk was found.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: reusing the existing BrowserStackLocal.exe on Windows before downloading.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

A rabbit checks the Windows trail
An .exe waits in a burrow pale
No download hops across the ground
Sync and async paths both find it found
The rabbit rests; the tests are sound

Comment @coderabbitai help to get the list of available commands.

Comment thread lib/LocalBinary.js Fixed
@pranay-v29
pranay-v29 marked this pull request as ready for review September 30, 2026 11:21
@pranay-v29
pranay-v29 requested a review from a team as a code owner September 30, 2026 11:21
@pranay-v29 pranay-v29 changed the title Reuse a running binary instead of replacing it LOC-7472 Reuse a running binary instead of replacing it Sep 30, 2026
@pranay-v29
pranay-v29 requested review from 07souravkunda and removed request for rounak610 and yashdsaraf September 30, 2026 11:22
binaryPath() picks the file to look for with this.windows, but that flag
was only set inside getBinaryFilename(), which runs on the download path.
On a fresh LocalBinary it was still undefined, so on Windows binaryPath()
checked for "BrowserStackLocal" without the extension, never found it,
and re-downloaded the binary on every start -- writing over
BrowserStackLocal.exe each time.

That is what the SDK-7713 logs show: a second run started fourteen
seconds after the first run's tunnel was up, went straight to the
download, and hit EBUSY because the first run was executing the file.
It also means every Windows start paid for a full download.

Set the flag in the constructor, as Local.js already does. This replaces
the --version probe from the previous commit, which only reused the
binary after the collision; with the right filename there is no
download to collide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pranay-v29 pranay-v29 changed the title LOC-7472 Reuse a running binary instead of replacing it Look for BrowserStackLocal.exe on Windows before downloading Sep 30, 2026
@pranay-v29 pranay-v29 changed the title Look for BrowserStackLocal.exe on Windows before downloading LOC-7472 Look for BrowserStackLocal.exe on Windows before downloading Sep 30, 2026
@pranay-v29

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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