Skip to content

[BUG] Upgrade 0.28.0 -> 0.29.0 deletes all deployed skills: stale cleanup runs in a run where skill integration was skipped #2744

Description

@ahegyes

Describe the bug

Upgrading 0.28.0 → 0.29.0 and running apm install -g against the lockfile 0.28.0 wrote deletes every previously deployed skill, from every target root, and reports success.

In the same run APM prints N hook(s) integrated but no skill(s) integrated line at all — skill integration is silently skipped — while the stale-cleanup phase still runs and removes the skill files the previous install had deployed ([i] Cleaned 24 stale files from <dep>). The lockfile is then rewritten to match that skill-less state, so subsequent apm install runs are consistent no-ops and never restore the files. Deleting the lockfile and reinstalling is the only recovery I found.

The trigger is the dependency content_hash recorded in the lockfile. For an identical dependency at an identical pinned commit, 0.29.0 computes a different content_hash than 0.28.0. When the value in the lockfile is 0.28.0's, skills are skipped and cleaned; when it is 0.29.0's, they are integrated normally. That is the only field that differs between the two lockfiles.

To Reproduce

Fully isolated via HOME redirection, with a single public dependency:

# $HOME/.apm/apm.yml
name: e2e-test
version: 1.0.0
description: repro
author: test
targets:
  - claude
  - codex
dependencies:
  apm:
    - DietrichGebert/ponytail#2ed6c52c9d7e5e56942508591085fd45dea277d3
  mcp: []
includes: auto
scripts: {}
  1. With 0.28.0: apm install -g → 6 skills in .claude/skills/, 6 in .agents/skills/; log says 6 skill(s) integrated -> .agents/skills/, .claude/skills/.
  2. With 0.29.0, same HOME, same manifest, unchanged lockfile: apm install -g
    0 skills in both roots. Log shows 2 hook(s) integrated (6 adopted), no skill line, and
    [*] Installed 1 APM dependency (24 stale files cleaned) in 0.1s. Exit code 0.
  3. Run 0.29.0 again → still 0 skills, (files unchanged), success. No self-healing.

The 24 removed lockfile deployment rows are exactly the skill entries in both roots; the 18 retained rows are exactly the hook entries. Hooks are unaffected.

Isolating control (one variable)

Rebuild state with 0.28.0 (6/6 skills), then patch only the dependency's content_hash in the 0.28.0-written lockfile to the value 0.29.0 computes — leaving apm_version: 0.28.0 and every other byte unchanged — then run 0.29.0:

lockfile 0.29.0 result skill line in log
as written by 0.28.0 6/6 → 0/0, 24 stale files cleaned absent
identical, content_hash patched to 0.29.0's 6/6 → 6/6, no cleanup 6 skill(s) integrated

A normalized diff of a 0.28.0-written and a 0.29.0-written lockfile for the same inputs differs in exactly one line — that content_hash. The 24 skill deployment rows are byte-identical in both.

Expected behavior

A content_hash written by an older APM should not cause deployed files to be deleted. Either the dependency is re-integrated (restoring the files) or it is left alone; the current behaviour skips integration and cleans, which is the one combination that loses data.

More generally, the stale-cleanup phase should not delete a primitive's files in a run where that primitive's integration did not execute.

Environment

  • OS: macOS 26.6.2
  • Python Version: 3.14.7
  • APM Version: 0.28.0 → 0.29.0 (0.29.0 is 518895ab7b)

Additional context

Observed first on a real user-scope install where this removed ~113 deployed skills across two roots; the reproduction above is a minimal synthetic version with a single public dependency. Severity feels high because apm install -g is commonly run unattended by wrapper tooling, the exit code is 0, and the printed summary reads as success.

I did not trace which code path skips skill integration on a content_hash mismatch, so I am reporting the measured behaviour and the isolating control rather than proposing a specific fix location.

Possibly related but distinct: #2743 (legacy ledger synthesis labels .agents/ paths with target agents). That one is an attribution defect and reproduces independently; it is not what drives this deletion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions