Fix Windows antivirus file-lock errors during apm install#440
Merged
danielmeppiel merged 8 commits intomainfrom Mar 26, 2026
Merged
Fix Windows antivirus file-lock errors during apm install#440danielmeppiel merged 8 commits intomainfrom
danielmeppiel merged 8 commits intomainfrom
Conversation
Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/apm/sessions/ed057371-b39d-466e-ae77-9054bb221be2
Create centralized src/apm_cli/utils/file_ops.py with robust_rmtree, robust_copytree, and robust_copy2 that retry on transient file-lock errors (WinError 32, WinError 5, EBUSY) with exponential backoff. Update github_downloader.py to use these wrappers in all download paths. Update path_security.py safe_rmtree to use robust_rmtree. Add 33 unit tests covering retry logic, error classification, and backoff behavior. Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/apm/sessions/ed057371-b39d-466e-ae77-9054bb221be2
…te imports Co-authored-by: danielmeppiel <51440732+danielmeppiel@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/apm/sessions/ed057371-b39d-466e-ae77-9054bb221be2
Copilot
AI
changed the title
[WIP] Fix corporate environment breaking path unpacking
Fix Windows antivirus file-lock errors during apm install
Mar 24, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens apm install (and related download/copy paths) against transient file-lock errors on Windows caused by antivirus/endpoint protection, by introducing retry-aware filesystem wrappers and adopting them in the GitHub/Artifactory downloader paths.
Changes:
- Added
apm_cli.utils.file_opswith exponential-backoff retry wrappers forrmtree/copytree/copy2. - Updated
github_downloader(andsafe_rmtree) to use the robust wrappers instead of rawshutilcalls. - Added unit tests for retry/error-classification behavior and documented Windows AV troubleshooting; added a changelog entry.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/utils/file_ops.py |
New retry-aware wrappers for common file operations to mitigate transient lock errors. |
src/apm_cli/deps/github_downloader.py |
Swaps deletion/copy operations in download flows to use robust wrappers. |
src/apm_cli/utils/path_security.py |
Routes user-controlled deletions through ensure_path_within + robust_rmtree. |
tests/unit/test_file_ops.py |
Adds unit coverage for classification, retry/backoff, and downloader delegation. |
docs/src/content/docs/getting-started/installation.md |
Documents Windows file-lock troubleshooting and APM_DEBUG diagnostics. |
CHANGELOG.md |
Adds an Unreleased “Fixed” entry for the Windows AV lock issue. |
Comments suppressed due to low confidence (2)
tests/unit/test_file_ops.py:297
- This test assumes a POSIX
/tmpexists. Usetmp_path / <name>(or another platform-neutral temp dir) to avoid failures on Windows and keep the test hermetic.
def test_nonexistent_directory_onerror_handles_silently(self):
"""rmtree with onerror callback handles missing dir silently."""
# shutil.rmtree with an onerror callback suppresses ENOENT,
# so robust_rmtree also does not raise for non-existent paths.
robust_rmtree(Path("/tmp/definitely-does-not-exist-apm-test"))
tests/unit/test_file_ops.py:486
- This test uses a hard-coded
/tmp/...path. For cross-platform compatibility (and to avoid touching global temp locations), prefer usingtmp_pathto construct the target path.
with patch("apm_cli.utils.file_ops.shutil.rmtree",
side_effect=PermissionError("denied")):
# Should not raise
_rmtree("/tmp/nonexistent-apm-test-dir")
- Remove unused imports (tempfile, MagicMock, call) from test_file_ops.py - Replace hardcoded /tmp/ paths with pytest tmp_path fixture - Clarify ignore_errors docstring in robust_rmtree - Replace em dash (U+2014) with ASCII in path_security.py docstring Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused _DEFAULT_MAX_RETRIES import in test_file_ops.py - Use onerror=_on_readonly_retry in robust_copytree cleanup instead of ignore_errors=True, preventing silent partial-dest retention that could defeat the retry loop on Windows AV locks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the 7-line auth troubleshooting block in installation.md with a 1-line cross-reference to authentication.md#troubleshooting. Removes content duplication while keeping the Windows AV block in place (install failure mode -- users look here first). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
On Windows with endpoint protection software (Symantec, Defender, etc.),
apm installfails with[WinError 32] The process cannot access the file because it is being used by another processwhen AV scans temp files during package download/copy.Root cause: Raw
shutil.copytree/copy2/rmtreecalls in download paths have no retry logic for transient file locks.Changes
src/apm_cli/utils/file_ops.py— stdlib-only leaf module with retry-aware wrappers:_is_transient_lock_error()— classifies WinError 32/5 and Unix EBUSY_retry_on_lock()— exponential backoff loop (0.1s initial, 2x, 2.0s cap, 5 retries ≈ 3.1s worst case)robust_rmtree()/robust_copytree()/robust_copy2()— drop-in replacements; copytree cleans partial dest between retriesgithub_downloader.py—_rmtree()delegates torobust_rmtree;download_subdirectory_packageand both artifactory paths use robust wrapperspath_security.py—safe_rmtree()composes containment check +robust_rmtree(security × reliability)Type of change
Testing
33 new unit tests covering error classification, retry/backoff behavior, partial cleanup,
ignore_errorssemantics, and_rmtreedelegation.🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.