Skip to content

[BUG] Update deletes unedited skill files it declined to overwrite for lack of a deployment receipt #3422

Open

Description

@lkshrk

Describe the bug

When a deployed skill has no materialization receipt (an installation made before receipts existed, or after the receipt was removed as the "installation binding does not match" error advises), updating the package deletes the skill's unedited files instead of updating or preserving them.

In one apm install -g:

  1. The writer refuses to overwrite each existing file without evidence: Skipped .claude/skills/demo/SKILL.md: no trustworthy local deployment baseline.
  2. The skipped files are not in the new lock's deployed_files.
  3. Stale cleanup then removes every old-lock file that is not in the new set and still matches its recorded hash, which includes the files the writer just declined to touch.

The command exits 0 with Installed 1 APM dependency (3 stale files cleaned), leaving the skill without its SKILL.md. A second apm install -g deploys the files normally because they no longer exist. A file the user really edited survives, because cleanup sees the hash mismatch, so this is not loss of user edits. But the "no trustworthy baseline" protection has no effect for unedited files, and the skill is broken until the next run.

On a real host this hit an updated skill with 54 files: one file was left until a second sync.

To Reproduce

Using a throwaway home and a local package:

sb=$(mktemp -d); export HOME=$sb/home
mkdir -p $HOME/.apm $sb/pkg/.apm/skills/demo
printf 'name: demo-pkg\nversion: 1.0.0\n' > $sb/pkg/apm.yml
printf -- '---\nname: demo\ndescription: demo skill\n---\nv1\n' > $sb/pkg/.apm/skills/demo/SKILL.md
echo v1 > $sb/pkg/.apm/skills/demo/a.md
echo v1 > $sb/pkg/.apm/skills/demo/b.md
printf 'name: sandbox\nversion: 1.0.0\ndependencies:\n  apm:\n  - path: %s\ntargets:\n- claude\n' "$sb/pkg" > $HOME/.apm/apm.yml
cd $HOME/.apm && apm install -g                  # deploys SKILL.md, a.md, b.md and a receipt

rm $HOME/.apm/deployment-receipts/*.json          # same state as a pre-receipt installation
sed -i 's/1.0.0/1.1.0/' $sb/pkg/apm.yml
echo v2 > $sb/pkg/.apm/skills/demo/a.md
echo v2 > $sb/pkg/.apm/skills/demo/c.md
apm install -g
ls $HOME/.claude/skills/demo                     # only c.md; SKILL.md, a.md and b.md are gone
apm install -g
ls $HOME/.claude/skills/demo                     # all four files again

Output of the second install:

[i] Cleaned 3 stale deployed files for <sb>/pkg
  [!] Skipped .claude/skills/demo/SKILL.md: no trustworthy local deployment baseline
  [!] Skipped .claude/skills/demo/a.md: no trustworthy local deployment baseline
  [!] Skipped .claude/skills/demo/b.md: no trustworthy local deployment baseline
  [!] [<sb>/pkg] Skipped removing skill directory
[*] Installed 1 APM dependency (3 stale files cleaned) in 0.1s.

b.md is unchanged between the two versions and is deleted as well.

Expected behavior

A file the writer declines to overwrite for lack of evidence is not removed by cleanup in the same run; it stays in place and stays owned, as it does for files with a hash mismatch. The skipped files are reported as needing attention, and the command does not report a clean install while it leaves a skill partially deployed.

Environment (please complete the following information):

  • OS: Linux 7.2 (Arch-based)
  • Python Version: 3.13.16
  • APM Version: v0.35.0, via a fork build (0.35.0+omni.6) whose only change in deployment_ledger.py adds two target path prefixes; skill integration and cleanup match v0.35.0.

Additional context

Receipts were introduced in #3339 / #3344. The two ways to reach this state are an installation that predates receipts and the recovery suggested by the binding error, which I reported separately in #3421 (on btrfs the binding breaks on every reboot). Together they mean a reboot followed by the suggested recovery partially removes the next updated skill.

Activity

  1. TyceHerrman commented on Nov 3, 2026

    @TyceHerrman

    Daniel Meppiel (@danielmeppiel) Could you take a look at this issue?

    I think the fix is to make a missing trustworthy deployment baseline stop installation before cleanup can delete files that APM refused to overwrite

  2. adelrodriguez commented on Nov 3, 2026

    @adelrodriguez

    Note

    🤖 Claude Opus 5.5 (on behalf of adelrodriguez)

    I hit the same result on APM 0.35.0, on macOS and on Ubuntu. Here the deployment receipt existed but listed a stale owner. It wasn't missing.

    apm update --global --yes exited 0 on both machines. It deleted the two skills whose content had changed upstream from every target folder, and it dropped their deployed_files entries from the lock. They stayed in skill_subset and in apm_modules. A following apm install --global deployed them again.

    The user-scope receipt on both machines points to the trigger. This setup was moved from a layout where the skills lived in ~/.apm/skills/<name> to a git dependency with a skills: subset. Most records still have active_owner: ~/.apm/skills/<name>. The only skills whose records name the git package as active_owner are the ones whose content changed after the move, the two from this update and three from earlier ones. Unchanged skills keep the old local owner.

    A clean HOME with the same apm.yml and the same old lock, where every receipt record names the git package, updated the same two skills without deleting anything.

    I don't have the full update output, so I can't confirm the "no trustworthy local deployment baseline" lines. The pattern fits the path in this issue, refused overwrite, then stale cleanup. A receipt whose owner no longer matches the dependency may be a second way into it, alongside a missing receipt. #3440 should cover it if it treats that refusal the same way.

  3. maro114510 commented on Nov 4, 2026

    @maro114510
    Contributor

    Hit this on macOS with 0.35.0. 170 skill files got deleted and the exit code was still 0.

    I replayed it in a throwaway HOME. 0.35.0 loses 172 of 434 files. With #3440 nothing is lost and the install stops with an error.

    Can this be accepted so #3440 can move forward?

  4. vlsi commented on Nov 5, 2026

    @vlsi

    The same happens on 0.35.0 in project scope, with no receipt removed by hand. A fresh clone of a repository that commits its deployed skills has no receipt on that machine. That is the state a CI job or a Renovate run starts from. The first apm install after the package's content changes deletes the committed SKILL.md and exits 0. So the note in #3391 that apm_package is unaffected holds only while the package content stays the same.

    sb=$(mktemp -d) && cd "$sb"
    mkdir -p pkg/.apm/skills/demo proj home-a home-b
    printf 'name: demo-pkg\nversion: 1.0.0\n' > pkg/apm.yml
    printf -- '---\nname: demo\ndescription: demo skill\n---\nv1\n' > pkg/.apm/skills/demo/SKILL.md
    cd proj && git init -q
    printf 'name: demo\nversion: 1.0.0\ndependencies:\n  apm:\n  - path: ../pkg\ntargets:\n- claude\n' > apm.yml
    HOME=$sb/home-a apm install > /dev/null && git add -A && git commit -qm install
    git clone -q "$sb/proj" "$sb/clone" && cd "$sb/clone"
    printf 'name: demo-pkg\nversion: 1.1.0\n' > ../pkg/apm.yml
    printf -- '---\nname: demo\ndescription: demo skill\n---\nv2\n' > ../pkg/.apm/skills/demo/SKILL.md
    HOME=$sb/home-b apm install; echo "exit=$?"
    git status --short

    Output of the last install, with apm-cli==0.35.0 from PyPI on macOS 26.5.1 and Python 3.10.21:

    [>] Installing dependencies from apm.yml...
    [>] Resolving ../pkg...
    [i] Targets: claude  (source: apm.yml)
      [+] ../pkg (local)
      |-- (files unchanged)
    [i] Cleaned 2 stale deployed files for ../pkg
      [!] Skipped .claude/skills/demo/SKILL.md: no trustworthy local deployment 
    baseline. Preserve your changes before reinstalling with --force.
      [i] Skipped inactive experimental resolver for target 'copilot-app' during 
    lockfile reconciliation.
      [i] Skipped inactive experimental resolver for target 'copilot-cowork' during 
    lockfile reconciliation.
    [*] Installed 1 APM dependency (2 stale files cleaned) in 0.3s.
    exit=0
     D .claude/skills/demo/SKILL.md
     M apm.lock.yaml
    

    With more than one target, every target's copy is deleted, but the output names only one of them. I saw this in a fresh worktree of a real repository with targets claude, codex, and cursor and one git dependency (package_type: apm_package) whose two skills changed upstream. apm update --yes exited 0 and left this git status --short:

     D .agents/skills/apm-authoring/SKILL.md
     D .agents/skills/english-developer-style/SKILL.md
     D .claude/skills/apm-authoring/SKILL.md
     D .claude/skills/english-developer-style/SKILL.md
     M apm.lock.yaml
    

    The output had one Skipped .agents/skills/<skill>/SKILL.md: no trustworthy local deployment baseline line per skill and Cleaned 4 stale deployed files per skill. No line named the .claude/skills copies. A plain apm install afterwards restored all four files, and the tree matched a deployment made by deleting the lock and running apm install --force.

    With #3440 at a92a7c6, both cases stop instead of deleting. The script above ends with exit 1 and an empty git status --short:

      [x] 1 package failed:
        +- Skipped .claude/skills/demo/SKILL.md: no trustworthy local deployment 
    baseline. Preserve your changes before reinstalling with --force.
    [x] Installation failed with 1 error(s) in 0.3s. No install transaction changes 
    were committed.
    

    In the real repository, apm update --yes exits 1, leaves the tree and the lock unchanged, and lists all four files, including the .claude/skills copies. apm update --yes --force then exits 0 and deploys the new versions. The summary line there says [x] 4 packages failed: for four files from two packages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions