Skip to content

fix: retry the Windows activation move on transient access denials - #14

Merged
BartoszBlizniak merged 2 commits into
mainfrom
fix/windows-activation-retry
Jul 30, 2026
Merged

fix: retry the Windows activation move on transient access denials#14
BartoszBlizniak merged 2 commits into
mainfrom
fix/windows-activation-retry

Conversation

@BartoszBlizniak

Copy link
Copy Markdown
Member

Summary

install.ps1's atomic activation moves the freshly extracted directory into its final versioned location using [System.IO.Directory]::Move. On GitHub-hosted windows-latest runners, this was observed to intermittently fail with:

Access to the path '...' is denied.

(UnauthorizedAccessException, sometimes IOException)

Directory.Move on Windows fails outright if any handle is open on the source or destination path. Microsoft Defender's real-time scan (or Windows Search indexing) can briefly hold a handle on a directory that was just extracted from a zip, causing the move to fail even though nothing in the install itself is wrong — a rerun always passes.

This adds a small bounded retry (Invoke-WithFilesystemRetry: up to 5 attempts, ~250ms apart, total <1.5s) around every Directory.Move in the activation sequence — staged-to-temp, old-to-backup, temp-to-final, and the rollback restore path — retrying only on UnauthorizedAccessException/IOException and rethrowing unchanged after the final attempt. Download retries already had their own Invoke-WithRetry helper with second-scale linear backoff, which is a poor fit here (too slow, and it retries on any exception), so this is a separate, narrower helper mirroring the same scriptblock-based shape.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • Other (please describe)

Validation

  • PowerShell AST parse check (install.ps1): pass.
  • Invoke-ScriptAnalyzer -Path install.ps1 -Settings PSGallery -EnableExit: pass, no findings.
  • pwsh -Command "Invoke-Pester tests/Install.Tests.ps1" on macOS: 5 passed, 0 failed, 27 skipped (Windows-tagged black-box tests self-skip on non-Windows hosts; the new activation-retry unit tests are not Windows-tagged and ran and passed locally, covering: success after N transient UnauthorizedAccessExceptions, success after a transient IOException, exhausting retries and rethrowing, and not retrying unrelated exception types).
  • sh -n install.sh: pass, no changes to install.sh.

Compatibility

None. No change to supported targets, options, environment variables, installation paths, or the four-line output contract. Activation now tolerates transient filesystem access errors instead of surfacing them as install failures; genuine (non-transient) access/IO errors still fail the install after 5 attempts, same as before but slightly slower to surface.

Checklist

  • I have added or updated tests where appropriate.
  • I have updated documentation and CHANGELOG.md where appropriate.
  • I have not included credentials, tokens, or other sensitive data.
  • I have called out any breaking change explicitly.

On GitHub-hosted windows-latest runners, Defender's real-time scan (or
Search indexing) can briefly hold a handle on the freshly extracted
directory, causing Directory.Move to throw UnauthorizedAccessException
or IOException during activation. Wrap the activation moves (including
the rollback/restore path) in a bounded retry (5 attempts, ~250ms
apart) that only catches those two exception types and rethrows after
the final attempt.
@BartoszBlizniak
BartoszBlizniak requested a review from a team as a code owner July 30, 2026 12:29
Copilot AI review requested due to automatic review settings July 30, 2026 12:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves the PowerShell installer’s reliability on Windows by tolerating transient filesystem access denials (e.g., Defender/indexer briefly holding handles) during the atomic “activation” directory moves.

Changes:

  • added Invoke-WithFilesystemRetry to retry only UnauthorizedAccessException/IOException with a small bounded delay.
  • wrapped activation/rollback Directory.Move operations in install.ps1 with the new filesystem retry helper.
  • added Pester unit tests for the new retry helper and documented the fix in CHANGELOG.md.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
install.ps1 Adds a targeted filesystem retry helper and applies it around activation/rollback directory moves.
tests/Install.Tests.ps1 Adds unit tests validating retry behavior, exhaustion/rethrow, and non-retry for unrelated exceptions.
CHANGELOG.md Notes the Windows activation transient access-denied retry behavior under Unreleased fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install.ps1
Comment thread install.ps1 Outdated
Directory.Move races with antivirus and indexer handles, and the previous
1s window (5 attempts, 250ms apart) is well short of what the established
implementations allow: MSBuild's Copy task retries the same two exception
types 10 times 1s apart, and rustup retries renames for tens of seconds
after hitting exactly this failure mode. Match MSBuild's defaults, report
the attempt count when the window is exhausted, and reject attempt and
delay values that would skip the action or sleep backwards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BartoszBlizniak
BartoszBlizniak merged commit fe896af into main Jul 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants