Skip to content

[Bug]: the Windows removal retry budget is 2.5 seconds and is exceeded under shard load #4789

Description

@lidge-jun

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

  1. Run the Windows leg with six concurrent shards (lane=all dispatch of ci.yml).
  2. Observe a shard failing in removeTreeWithRetry with EPERM on a mkdtemp directory that a server fixture used.
  3. 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 icacls child directly where the caller owns it, which is the mechanism flushConfigDirHardening already provides for server.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

  1. added
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Sep 16, 2026
  2. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 62 / 80

    이 이슈는 Windows에서 설정 디렉터리를 지울 때 나는 EPERM/EBUSY 레이스를 테스트 헬퍼가 얼마나 오래 기다려 주느냐에 대한 보고다. 지금 dev tip(0c4d63afa)의 tests/helpers/remove-tree.ts 5–6행은 REMOVE_ATTEMPTS = 50, REMOVE_RETRY_DELAY_MS = 50이라서 합계 2.5초다. 같은 파일 4행의 TRANSIENT_REMOVE_CODES는 EPERM/EBUSY/ENOTEMPTY만 재시도하고, 38행에서 마지막 시도가 실패하면 그대로 다시 던진다.

    왜 이런 재시도가 있냐면 src/config/paths.ts 55–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이 작성했습니다

  3. lidge-jun commented on Sep 16, 2026

    @lidge-jun
    OwnerAuthor

    Landed via #4796 at dc9d1fa

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions