Skip to content

fix(cli): a browser install goes on when Windows holds a file for a moment - #7194

Merged
miguel-heygen merged 4 commits into
mainfrom
drenderq/browser-install-lock-retry
Nov 5, 2026
Merged

miguel-heygen merged 4 commits into
mainfrom
drenderq/browser-install-lock-retry

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

On Windows, hyperframes browser ensure (and any render that installs the browser first) now waits a moment and tries again when a file in the install is held by another program, instead of failing at once with EBUSY, EPERM or EACCES.

Why

Virus scanners and indexers open freshly unzipped files for a few seconds. The install unzips into a staging folder, then renames it into the cache. If a scanner holds a file at either step, the unzip, the cleanup of the staging folder or the rename throws, and the install gives up with "Failed to install chrome-headless-shell". Nothing retried a lock error, so people on Windows saw a browser install fail that works a few seconds later.

How

  • packages/cli/src/browser/manager.ts: isLockedFileError (Windows only; EBUSY, EPERM, EACCES by code or message) and retryWhileLocked, up to 5 tries with waits of 250, 500, 1000 and 2000 ms, clearing the half-written staging folder between tries.
  • It wraps the two steps that touch files: the install into staging, and the move into the cache. Any other error, and any error on macOS or Linux, is thrown on the first try as before.
  • The staging cleanup now retries its own removal (maxRetries: 3).

Size

Small: one helper and two call sites in the one file that does the install.

What I measured

  • fails without the fix: packages/cli/src/browser/manager.test.ts › retryWhileLocked (6 tests: retries EBUSY, EPERM and EACCES on Windows and then succeeds, gives up after the last try, does not retry other errors, does not retry off Windows)
  • manager.test.ts: 81 passed, three runs in a row, on a Linux box. The 6 new tests fail with the helper removed.
  • Formatter check on both changed files: clean.
  • On a GitHub windows-latest runner, a helper holds chrome-headless-shell.exe open for 3 s as soon as it is unzipped. Released 0.9.13: browser ensure exits 1 with EBUSY: resource busy or locked, unlink '...\.chrome-staging-...\chrome-headless-shell.exe'. This branch: the same hold, browser ensure exits 0, "Ready to render."

What I did NOT exercise

  • A real virus scanner holding a file on a user's machine; the runner's hold is a stand-in, and the runner has Defender real-time protection off.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at b7bb8699. No blockers.

  • Fails without the fix: The body names manager.test.ts › retryWhileLocked. Without the helper those 6 tests can't import it and fail. The windows-latest run with a 3 s hold covers the wiring that the unit tests don't.
  • Move retry is safe: moveStagedInstallIntoCache only gets retryWhileLocked with the default no-op onRetry, so staging isn't cleared between move attempts. A failed rename leaves staged.path in place, so the next attempt has something to move. If the first rename (target → aside) is what's locked, for example because a running old Chrome holds the cache dir, it now fails after about 3.75 s of waits instead of at once. Same outcome as before, just later.
  • Install retry: Lock errors go to the outer loop, while stall and corrupt-archive errors are still handled inside it. Each lock retry clears staging and downloads again. That's acceptable for a short scanner hold, but see the nit below.
  • macOS and Linux: isLockedFileError returns false off win32, so nothing changes there.

Nits (none block):

  1. Fallow audit is red because of this PR: packages/cli/src/browser/manager.ts:758, isLockedFileError is exported but never imported. Dropping export should clear it. It isn't a required check, but it will stay red on main.
  2. On Windows, a genuine EPERM/EACCES (for example, a cache dir the user can't write) is now tried 5 times, with a fresh download each time if it fails after the archive lands. That's worth a comment, or a separate attempt limit for the install step.
  3. The clearStaging call in onRetry can itself throw EBUSY after its 3 rmSync retries. That ends the loop with the rm error rather than the install error. It's rare and still a clear message.

CI at this head when I reviewed: regression, Player perf and PR captures had passed, and Lint, Format and Test reachability in CI had passed. The CLI tests, Typecheck, Build and Windows render verification were still running.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 119 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

Unstable (2)

  • seqplainkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqab-none-center-r0-root-z100: tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at bb1777e0. This re-reviews my approval at b7bb8699.

  • What changed: b7bb8699..bb1777e0 is one commit (fix(cli): keep the lock check private) that sits directly on the approved head. It changes packages/cli/src/browser/manager.ts only:
    • isLockedFileError is no longer exported, which clears the Fallow unused-export finding.
    • Its doc comment now says a real permission error is retried too, then thrown. That covers nit 2 from my earlier review.
  • Tests: No test imported isLockedFileError, and the tests still reach it through retryWhileLocked. The 6 tests named on the "fails without the fix:" line are unchanged.
  • CI: Every Actions workflow at this head has passed: CI, regression, preview-regression, Windows render verification, CodeQL, Player perf and PR captures.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve at bd67aa70. This re-reviews my approval at bb1777e0.

bb1777e0..bd67aa70 is one commit (chore(cli): wrap the lock-check comment) that sits directly on the approved head. Its only change is to split the isLockedFileError doc comment across two lines in packages/cli/src/browser/manager.ts. No code changed, and the tests and the "fails without the fix:" line are unchanged.

CI at this head when I reviewed: regression, Player perf, PR captures and Comments had passed. CI, CodeQL, Windows render verification and preview-regression were still running.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 2372fc3 Nov 5, 2026
84 checks passed
@miguel-heygen
miguel-heygen deleted the drenderq/browser-install-lock-retry branch November 5, 2026 02:08
@heygengenesis heygengenesis Bot mentioned this pull request Nov 5, 2026
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 5, 2026
…eview

Adopts heygen-com#7199 (delivery-PCM duration normalization), heygen-com#7194 (browser
install retries while Windows holds a file), the Studio marquee and
timeline range-selection series, waapi initial-state, lint media and
CLI check/contrast fixes, and skills docs. Not adopted with ledger
reasons: heygen-com#7203, heygen-com#7207, heygen-com#7202, heygen-com#7197; limitDeliveryPeaks and the
neverStyledStarts cache are left out because the fork lacks their
prerequisites. Watermark advances to aa34cff / PR heygen-com#7190.

Co-authored-by: WaterrrForever <miao.yang@heygen.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: kevin <5299031+kevin9327@users.noreply.github.com>
Co-authored-by: Jason <jason.easyai@gmail.com>
Co-authored-by: cooleryu <53998515+cooleryu@users.noreply.github.com>
Co-authored-by: Vincent Yip <realsharkguy@gmail.com>
Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant