Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 20cd707

Browse files
committed
fix: add retry logic to copyDir/copyPaths for EBUSY errors on Windows
The Windows CI bundle step fails with EBUSY when antivirus or indexing services hold brief locks on files during copyFileSync. Add a copyFileWithRetry helper (matching the existing rmDir retry pattern) that retries up to 5 times with exponential backoff for EBUSY, EPERM, and EACCES errors.
1 parent c7021f5 commit 20cd707

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

packages/build/src/esbuild.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@ import { execSync } from "child_process"
44

55
import { ViewsContainer, Views, Menus, Configuration, Keybindings, contributesSchema } from "./types.js"
66

7+
/**
8+
* Copy a single file with retry logic to handle transient Windows file-locking
9+
* errors (EBUSY, EPERM, EACCES) that occur when antivirus or indexing services
10+
* hold brief locks on files during CI builds.
11+
*/
12+
function copyFileWithRetry(src: string, dst: string, maxRetries: number = 5): void {
13+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
14+
try {
15+
fs.copyFileSync(src, dst)
16+
return
17+
} catch (error) {
18+
const isRetryable =
19+
error instanceof Error &&
20+
"code" in error &&
21+
((error as NodeJS.ErrnoException).code === "EBUSY" ||
22+
(error as NodeJS.ErrnoException).code === "EPERM" ||
23+
(error as NodeJS.ErrnoException).code === "EACCES")
24+
25+
if (!isRetryable || attempt === maxRetries) {
26+
throw error
27+
}
28+
29+
const baseDelay = process.platform === "win32" ? 200 : 100
30+
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), 2000)
31+
console.warn(`[copyFileWithRetry] Attempt ${attempt} failed for ${src}, retrying in ${delay}ms...`)
32+
33+
// Synchronous sleep (same pattern as rmDir).
34+
const start = Date.now()
35+
36+
while (Date.now() - start < delay) {
37+
/* Busy wait */
38+
}
39+
}
40+
}
41+
}
42+
743
function copyDir(srcDir: string, dstDir: string, count: number): number {
844
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
945

@@ -16,7 +52,7 @@ function copyDir(srcDir: string, dstDir: string, count: number): number {
1652
count = copyDir(srcPath, dstPath, count)
1753
} else {
1854
count = count + 1
19-
fs.copyFileSync(srcPath, dstPath)
55+
copyFileWithRetry(srcPath, dstPath)
2056
}
2157
}
2258

@@ -98,7 +134,7 @@ export function copyPaths(copyPaths: [string, string, CopyPathOptions?][], srcDi
98134
const count = copyDir(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath), 0)
99135
console.log(`[copyPaths] Copied ${count} files from ${srcRelPath} to ${dstRelPath}`)
100136
} else {
101-
fs.copyFileSync(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath))
137+
copyFileWithRetry(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath))
102138
console.log(`[copyPaths] Copied ${srcRelPath} to ${dstRelPath}`)
103139
}
104140
} catch (error) {

0 commit comments

Comments
 (0)