Repository navigation
[Bug]: the Windows removal retry budget is 2.5 seconds and is exceeded under shard load #4789
Description
Activity
- addedplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)OS/service/tray/ACL (Windows-heavy, not Windows-only)
on 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).메인테이너의 판단이 필요한 지점
- icacls 소유 호출부에서 flush를 강제할지, 헬퍼 예산만 키울지.
- 지수 백오프 vs Windows-only 천장 상향 중 무엇으로 갈지.
- 프로덕션 언인스톨/홈 이동까지 같은 이슈 범위로 묶을지, 테스트 CI flake로 좁힐지.
너의 추천
KEEP. 먼저server-management-auth등 icacls를 건드리는 fixture teardown 앞에flushConfigDirHardeningForTests()를 넣고, 그래도 샤드 flake가 남으면 Windows에서만 지수 백오프(짧은 시작·상한 수 초)로removeTreeWithRetry를 바꿔라. 숫자만 100×50으로 올리는 것은 최후 수단이다.이 댓글은 grok-bot이 작성했습니다
- added a commit that references this issue
on Sep 16, 2026 - addedlanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainOriginal PR closed after landing via a maintainer merge train
on Sep 16, 2026 - added a commit that references this issue
on Sep 17, 2026
Client or integration
Other
Area
Platform (Windows / macOS / Linux)
Summary
removeTreeWithRetryabsorbs the Windows filesystem-release race thatsrc/config/paths.ts:57documents:hardenConfigDir()spawnsicacls.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 = 50atREMOVE_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: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
icaclschild directly is a maintainer decision.Reproduction
lane=alldispatch ofci.yml).removeTreeWithRetrywith EPERM on amkdtempdirectory that a server fixture used.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. Run35050601423completedtests/server/server-management-auth.test.tssuccessfully on Windows; run35053542913failed it with the EPERM above. The only difference between the two runs was the absorbed upstreamdevcommits and the resulting shard distribution.Suggested directions, in the order they seem worth considering:
icaclschild directly where the caller owns it, which is the mechanismflushConfigDirHardeningalready provides forserver.stop, rather than polling blind.Version
2.57.0
Operating system
Windows (GitHub Actions
windows-latestshards)Provider and model
Not provider-specific.
Logs or error output
Screenshots and supporting files
Not applicable; the annotation above is the full evidence.
Redacted configuration
Not applicable.
Checks