Repository navigation
[Bug]: the Windows removal retry budget is 2.5 seconds and is exceeded under shard load #4789
Description
Client or integration
Other
Area
Platform (Windows / macOS / Linux)
Summary
removeTreeWithRetry absorbs the Windows filesystem-release race that src/config/paths.ts:57 documents: hardenConfigDir() spawns icacls.exe, which holds the config directory open, and "anything that removes or renames that directory after a clean shutdown — a test fixture teardown, an uninstaller, a home move — gets EPERM/EBUSY unless the process that started the child also waits for it."
The retry budget is REMOVE_ATTEMPTS = 50 at REMOVE_RETRY_DELAY_MS = 50, so the helper gives that race 2.5 seconds total (tests/helpers/remove-tree.ts:5-6). Under six concurrent Windows shards that budget is not always enough, and the helper rethrows the EPERM it exists to absorb:
tests\helpers\remove-tree.ts:38
error: EPERM: operation not permitted, rm 'C:\Users\RUNNER~1\AppData\Local\Temp\ocx-management-auth-<id>'
at removeTreeWithRetry (tests/helpers/remove-tree.ts:38:83)
at tests/server/server-management-auth.test.ts:206:17
The budget appears to be tuned for a lightly loaded machine. The same concern applies outside tests: an uninstall or a home move issued while the machine is busy can exceed 2.5 seconds of handle retention for the same reason, and that path has no shard to retry it.
This is a pre-existing condition rather than a regression. It surfaced during a stacked-lane CI run and is filed rather than fixed, because whether the right answer is a larger budget, exponential backoff, or waiting on the icacls child directly is a maintainer decision.
Reproduction
- Run the Windows leg with six concurrent shards (
lane=alldispatch ofci.yml). - Observe a shard failing in
removeTreeWithRetrywith EPERM on amkdtempdirectory that a server fixture used. - Re-run the failed shard; it passes, because the race is timing-dependent rather than deterministic.
Evidence that this is load-dependent and not caused by the code under test: two dispatch runs of the same lane, with byte-identical tests/helpers/remove-tree.ts, disagreed. Run 35050601423 completed tests/server/server-management-auth.test.ts successfully on Windows; run 35053542913 failed it with the EPERM above. The only difference between the two runs was the absorbed upstream dev commits and the resulting shard distribution.
Suggested directions, in the order they seem worth considering:
- Wait on the
icaclschild directly where the caller owns it, which is the mechanismflushConfigDirHardeningalready provides forserver.stop, rather than polling blind. - Replace the flat 50 x 50ms with a bounded exponential backoff, so a busy machine gets a longer tail without adding 2.5 seconds of latency to the common case.
- Keep the flat schedule but raise the ceiling on Windows only, where this race is the documented one.
Version
2.57.0
Operating system
Windows (GitHub Actions windows-latest shards)
Provider and model
Not provider-specific.
Logs or error output
Windows shard 5/6 failed on attempt 1 (exit 1); assertion failures are not retried.
tests\helpers\remove-tree.ts:38
error: EPERM: operation not permitted, rm 'C:\Users\RUNNER~1\AppData\Local\Temp\ocx-management-auth-<id>'
at removeTreeWithRetry (tests/helpers/remove-tree.ts:38:83)
at <anonymous> (tests/server/server-management-auth.test.ts:206:17)Screenshots and supporting files
Not applicable; the annotation above is the full evidence.
Redacted configuration
Not applicable.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Sep 16, 2026
리뷰 · 우선순위 62 / 80
이 이슈는 Windows에서 설정 디렉터리를 지울 때 나는 EPERM/EBUSY 레이스를 테스트 헬퍼가 얼마나 오래 기다려 주느냐에 대한 보고다. 지금
devtip(0c4d63afa)의tests/helpers/remove-tree.ts5–6행은REMOVE_ATTEMPTS = 50,REMOVE_RETRY_DELAY_MS = 50이라서 합계 2.5초다. 같은 파일 4행의TRANSIENT_REMOVE_CODES는 EPERM/EBUSY/ENOTEMPTY만 재시도하고, 38행에서 마지막 시도가 실패하면 그대로 다시 던진다.왜 이런 재시도가 있냐면
src/config/paths.ts55–61행 근처 주석이 말해 준다.hardenConfigDir()가icacls.exe를 띄우면 그 프로세스가 디렉터리를 잡고 있고, Windows는 파일 잠금이 강제라서 자식이 끝나기 전에 지우거나 이름을 바꾸면 EPERM/EBUSY가 난다.flushConfigDirHardening/flushConfigDirHardeningForTests는 그 비행(flight)을 기다리라고 이미 있다. 서버stop쪽은 그 경로를 쓰는데, 테스트 fixture teardown은 대개removeTreeWithRetry만 돌린다.이슈 근거도 설득력 있다. 같은
remove-tree.ts바이트인데 Windows 6샤드 CI 한 번은server-management-auth가 통과하고(런35050601423), 다른 번은 임시ocx-management-auth-*디렉터리에서 EPERM으로 터진다(런35053542913). 재실행하면 통과하니 결정적 버그라기보다 부하·샤드 배치에 민감한 타이밍 문제다. 방금 tip에 들어온 #4747 보호-home 거부와도 맞닿아 있다 —removeTreeWithRetry가 이제 먼저assertRemovalOutsideProtectedTrees를 호출하지만, 재시도 예산 자체는 그대로 2.5초다.고치는 방향은 이슈가 적은 세 가지로 잘 정리돼 있다. (1) 호출자가
icacls를 연 자리에서는flushConfigDirHardening*를 teardown 앞에 두고, (2) 평탄 50×50ms 대신 상한 있는 지수 백오프, (3) Windows에서만 천장만 올린다. 프로덕션 언인스톨/홈 이동도 같은 잠금을 맞을 수 있다고 적어 둔 점은 맞지만, 지금 재현 증거는 테스트 헬퍼·CI 샤드에 더 가깝다. types.ts/config.ts 분할 캠페인과는 무관하고 닫을 중복도 안 보인다.라인 5-6 -
REMOVE_ATTEMPTS×REMOVE_RETRY_DELAY_MS= 2.5초 고정 예산이 6샤드 부하에서 부족할 수 있음.라인 30-39 - 평탄 재시도만 하고, 마지막 실패 시 EPERM을 그대로 재throw.
경로
src/config/paths.ts~55-67 - icacls flight를 기다리는flushConfigDirHardening*는 이미 있는데 fixture teardown이 안 쓰는 경우가 핵심.경로
tests/server/server-management-auth.test.ts~206 - 이슈가 가리킨 EPERM 재현 지점(mkdtemp 홈 teardown).메인테이너의 판단이 필요한 지점
너의 추천
KEEP. 먼저
server-management-auth등 icacls를 건드리는 fixture teardown 앞에flushConfigDirHardeningForTests()를 넣고, 그래도 샤드 flake가 남으면 Windows에서만 지수 백오프(짧은 시작·상한 수 초)로removeTreeWithRetry를 바꿔라. 숫자만 100×50으로 올리는 것은 최후 수단이다.이 댓글은 grok-bot이 작성했습니다
fix(test): contain and reclaim Windows temporary roots (#4785, #4789) (…
lidge-jun commentedon Sep 16, 2026
Landed via #4796 at dc9d1fa
fix(test): wait for the icacls child before removing the management-a…
fix(lib): wait for a killed subprocess to actually die before abandon…
fix(test): contain and reclaim Windows temporary roots (lidge-jun#4785,