Skip to content

Delete skill folders for removed packages#1767

Open
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam/feature-delete-skill-folders-on-uninstal
Open

Delete skill folders for removed packages#1767
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam/feature-delete-skill-folders-on-uninstal

Conversation

@sergio-sisternes-epam

Copy link
Copy Markdown
Collaborator

TL;DR

Teach the cleanup safety gate to recognise APM-deployed skill directories and safely remove them after individual files are deleted, instead of emitting a "Refused to remove directory entry" warning.

Problem (WHY)

When a package is removed from apm.yml and apm install runs, APM deletes individual files but refuses to delete directories that were deployed as skill bundles (e.g. .agents/skills/coding-standards-python/). The user sees:

[!] Refused to remove directory entry .agents/skills/coding-standards-python:
    APM only deletes individual files.

This happens because services.py records both the skill directory entry and individual file entries in deployed_files, but Gate 2 in cleanup.py unconditionally rejects all directory entries as potentially poisoned lockfile entries -- even those APM itself created.

Closes #1483

Approach (WHAT)

  • Modified Gate 2 in remove_stale_deployed_files() to defer skill directory entries (matching <prefix>/skills/<name>) to a second pass instead of immediately rejecting them.
  • After all file-keyed deletions complete, each deferred directory is safely removed when:
    • It no longer exists (already cleaned by the first pass)
    • It is empty (all files deleted in the first pass)
    • All remaining files are APM-tracked with matching content hashes
  • Directories containing user-created or user-edited files are skipped with a detailed warning.
  • Non-skill directory entries retain the existing security rejection.

Implementation (HOW)

src/apm_cli/integration/cleanup.py

  • Added _is_skill_directory_entry() -- detects <prefix>/skills/<name> patterns
  • Added _safe_remove_skill_directory() -- encapsulates safe removal logic (empty check, provenance verification, rmtree)
  • Added _emit_dir_failure() and _strip_sha256_prefix() helpers
  • Modified remove_stale_deployed_files():
    • Materialise stale_paths iterable into list + set (was single-use)
    • Gate 2 now defers skill directories to deferred_dirs list
    • Second pass after file deletion processes deferred directories

tests/unit/integration/test_cleanup_skill_dirs.py (new)

19 tests covering:

  • _is_skill_directory_entry pattern matching (10 cases)
  • Skill directory removed when empty after file deletion
  • Skill directory removed when all contents have matching hashes
  • Skill directory NOT removed when user files present
  • Skill directory NOT removed when hash mismatch
  • Non-skill directories still rejected
  • Already-gone directories handled gracefully
  • Backward compat with legacy lockfiles (no hashes)
  • Path traversal still rejected by Gate 1

Trade-offs

  • Only skill directories get the deferred treatment; other directory entries retain the strict security rejection. This is intentional -- skills are the only primitive type APM deploys as directories.
  • No lockfile schema change needed -- existing deployed_files + deployed_file_hashes provide sufficient ownership/provenance data.

Validation

  • All 19 new tests pass
  • All 20 existing cleanup tests pass (no regressions)
  • All 1647 unit/integration tests pass
  • Ruff check + format clean
  • Pylint R0801 duplication check clean

How to test

  1. Create a project with a skill dependency (e.g. a package containing a skill)
  2. Run apm install to deploy the skill
  3. Remove the dependency from apm.yml
  4. Run apm install again
  5. Verify the skill folder is cleanly removed (no "Refused to remove directory entry" warning)
  6. Repeat step 1-4 but edit a file inside the skill folder before step 4 -- verify the directory is preserved with a warning about the edited file

Teach the cleanup safety gate to recognise APM-deployed skill
directories and safely remove them after individual files are
deleted, instead of emitting a 'Refused to remove directory entry'
warning.

The fix defers skill directory entries (matching <prefix>/skills/<name>)
to a second pass within remove_stale_deployed_files. After all file-keyed
deletions complete, each deferred directory is removed when:
- it no longer exists (already cleaned), or
- it is empty (all files deleted in the first pass), or
- all remaining files are APM-tracked with matching content hashes.

Directories containing user-created or user-edited files are skipped
with a detailed warning listing the blocking files. Non-skill directory
entries retain the existing security rejection.

Closes #1483

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 13, 2026 14:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates the stale-deployed-files cleanup helper to treat skill bundle directories (<prefix>/skills/<name>) as a special-case: defer them during the main deletion pass, then attempt safe directory removal after file deletions complete (to avoid the current “Refused to remove directory entry …” warning for skill bundles).

Changes:

  • Add skill-directory detection plus a deferred second pass that removes empty skill directories (or removes the whole tree when remaining contents are verified as APM-owned).
  • Add a dedicated unit test module covering skill-directory deferral/removal scenarios.
  • Add a CHANGELOG.md entry under Fixed describing the behavior change.

Reviewed changes

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

File Description
src/apm_cli/integration/cleanup.py Adds _is_skill_directory_entry() and deferred skill-directory cleanup logic in remove_stale_deployed_files().
tests/unit/integration/test_cleanup_skill_dirs.py New unit tests validating skill directory pattern matching and deferred cleanup behavior.
CHANGELOG.md Documents the new behavior under Unreleased -> Fixed.

Comment thread src/apm_cli/integration/cleanup.py Outdated
Comment thread src/apm_cli/integration/cleanup.py Outdated
Comment thread src/apm_cli/integration/cleanup.py
Comment thread tests/unit/integration/test_cleanup_skill_dirs.py Outdated
Comment thread CHANGELOG.md Outdated
Comment thread src/apm_cli/integration/cleanup.py
…tail

- Replace non-ASCII box-drawing characters in comments with ASCII
- Treat symlinks inside skill directories as blocking (user content)
- List blocking file paths in the diagnostic message (not just count)
- Fix test to actually exercise rmtree path (drop asset from stale)
- Add symlink blocking test
- Use PR number in CHANGELOG entry per convention

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

[FEATURE] Delete skill folders for removed packages

2 participants