Skip to content

Fix Windows antivirus file-lock errors during apm install#440

Merged
danielmeppiel merged 8 commits intomainfrom
copilot/fix-path-unpacking-issue
Mar 26, 2026
Merged

Fix Windows antivirus file-lock errors during apm install#440
danielmeppiel merged 8 commits intomainfrom
copilot/fix-path-unpacking-issue

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

Description

On Windows with endpoint protection software (Symantec, Defender, etc.), apm install fails with [WinError 32] The process cannot access the file because it is being used by another process when AV scans temp files during package download/copy.

Root cause: Raw shutil.copytree/copy2/rmtree calls in download paths have no retry logic for transient file locks.

Changes

  • New 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 retries
  • github_downloader.py_rmtree() delegates to robust_rmtree; download_subdirectory_package and both artifactory paths use robust wrappers
  • path_security.pysafe_rmtree() composes containment check + robust_rmtree (security × reliability)
  • Docs — Windows AV troubleshooting section in installation guide
# Before: raw shutil, no retry
shutil.copytree(src, dst)

# After: transparent retry on transient locks
from apm_cli.utils.file_ops import robust_copytree
robust_copytree(src, dst)

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

33 new unit tests covering error classification, retry/backoff behavior, partial cleanup, ignore_errors semantics, and _rmtree delegation.


🔒 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.

Copilot AI and others added 3 commits March 24, 2026 13:12
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
Copilot AI requested a review from danielmeppiel March 24, 2026 13:29
@danielmeppiel danielmeppiel marked this pull request as ready for review March 24, 2026 13:53
@danielmeppiel danielmeppiel requested review from Copilot and removed request for danielmeppiel March 24, 2026 13:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_ops with exponential-backoff retry wrappers for rmtree/copytree/copy2.
  • Updated github_downloader (and safe_rmtree) to use the robust wrappers instead of raw shutil calls.
  • 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 /tmp exists. Use tmp_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 using tmp_path to 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")

danielmeppiel and others added 2 commits March 25, 2026 23:13
- 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>
@danielmeppiel danielmeppiel self-requested a review as a code owner March 26, 2026 00:29
danielmeppiel and others added 2 commits March 26, 2026 01:30
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>
@danielmeppiel danielmeppiel merged commit 7c14622 into main Mar 26, 2026
20 checks passed
@danielmeppiel danielmeppiel deleted the copilot/fix-path-unpacking-issue branch March 26, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Corporate environment breaks path unpacking

3 participants