I have reproduced the issue on the latest CLI version.
Yes, I am on the latest version
I have searched existing posts and this report is not a duplicate.
Yes, this isn’t a duplicate
In which of these areas are you experiencing a problem?
Extension
Expected behavior
app deploy builds and bundles all extensions and releases a new version.
Actual behavior
Deploys intermittently fail (roughly 1 in 10 runs for us) during bundling:
╭─ error ──────────────────────────────────────────────────────────────────────╮
│ │
│ Build step "Bundle UI Extension" failed: ENOENT: no such file or │
│ directory, lstat │
│ '<repo>/extensions/checkout-ui-extension/dist/<file>.js.map'
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Root cause: a race on the shared dist/ directory
For each ui_extension, deploy does three filesystem steps
(bundle-ui-step.ts and
extension-instance.ts):
- Build — esbuild writes
dist/<handle>.js+dist/<handle>.js.mapinto the extension directory’s shared localdist/(sourcemaps are on becauseui_extensionhas thegenerates_source_mapsfeature). - Copy into bundle —
bundle-ui-step.tsline 33:copyFile(localOutputDir, bundleOutputDir)recursively copies the entire shareddist/into that extension’s bundle staging folder. (copyFileis fs-extracopy, which readdirs the source thenlstats each entry.) - Relocate sourcemap —
keepBuiltSourcemapsLocally(extension-instance.ts:225–238):moveFile(bundleMap, localMap, {overwrite: true})moves the extension’s own.js.mapfrom the bundle folder back over the copy in the shareddist/. fs-extramovewithoverwritedeletes the destination, then renames — so the shareddist/<handle>.js.mapbriefly does not exist.
All handles in the directory run this concurrently. The race:
- Extension A is in step 2, recursively copying the shared
dist/. Its readdir sees<B>.js.map. - Extension B, already in step 3, deletes
dist/<B>.js.map(the delete half of move-with-overwrite). - A’s copy
lstats the now-missing file →ENOENT→ the whole deploy aborts.
Depending on timing the same race also surfaces as ENOENT: no such file or directory, copyfile '...<handle>.js.map' -> '<bundle>/...'.
The more extensions share one directory, the larger the shared dist/ and the more concurrent copy/move pairs, so the failure probability grows with exactly the multi-target setup the single-TOML layout encourages.
Reproduction steps
The failure can be demonstrated with nothing but Node and fs-extra. Build a small script that mimics what app deploy does for every UI extension in a shared directory, then run many trials.
Setup: create a temp workspace containing a single shared dist/ directory (representing the extension directory’s output folder) and an empty per-handle bundle area. Pick around six fake handle names — matching the number of ui_extension blocks that share one directory in a real app.
Per handle, run these three steps in order — this mirrors the CLI’s deploy pipeline exactly:
- Build: write
<handle>.jsand<handle>.js.mapinto the shareddist/. Use realistic sizes (tens of KB for the JS, a few hundred KB for the map) so the later copy takes long enough to be interruptible. - Copy into bundle: recursively copy the entire shared
dist/— not just that handle’s files — into the handle’s own bundle folder, using fs-extra’scopy. This is what the CLI’s “Bundle UI Extension” step does, and it’s the operation that eventually throws. - Relocate the sourcemap: move the handle’s own
.js.mapfrom its bundle folder back to the shareddist/, using fs-extra’smovewithoverwrite: true. This mirrors the CLI’skeepBuiltSourcemapsLocally. The overwrite option is the crux: fs-extra implements it as delete-the-destination-then-rename, so the shareddist/<handle>.js.mapbriefly doesn’t exist.
Run all handles concurrently, with small staggered start times (a few milliseconds apart) so they’re in different steps at the same moment — just like real extension builds finishing at different times. That’s one simulated deploy.
Repeat for 100–200 simulated deploys, using a fresh workspace each time, and collect any rejections.
Verbose output
╭─ error ──────────────────────────────────────────────────────────────────────╮
│ │
│ Build step "Bundle UI Extension" failed: ENOENT: no such file or │
│ directory, lstat │
│ '<repo>/extensions/checkout-ui-extension/dist/<file>.js.map'
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Operating system
Ubuntu (GitHub Actions ubuntu-latest); the bug is platform-independent
CLI version
4.5.2