fix(sync): the skills orphan sweep never deletes a harness's own skill dirs - #3712
Merged
Merged
Conversation
A full sync removed every non-dot directory under a harness's skills/ that was not in this run's sync set. That set shrank when Claude stopped getting flattened plugin skills (ebce589), which is right for the flattened copies, but the same readdir sweep also removes entries the sync never placed, such as Claude Code's own skills/synced bucket. The sweep now also requires the path to be in the previous full sync's manifest writtenTargets, the record the writers already keep, matching how the commands sweep bounds a dual-write target. No manifest means nothing is swept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eep on the manifest Review of the manifest bound: a directory the previous sync never recorded (every stale skill on an existing install, and any first sync) was no longer swept, with no warning. The sweep is back to removing any untrusted directory. The only thing it now spares is what the harness writes itself, named on the agent spec as ownedSkillDirs (Claude: synced). `agents prune cleanup skills` needs no change: it only lists directories with a SKILL.md at their root, and synced has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ound Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Independent re-review (code-reviewer subagent, non-author) of head aeca62f. VERDICT: APPROVE The earlier BLOCKER, where manifest gating skipped never-tracked orphans, is resolved. The sweep is unconditional again, with Claude's Reproductions (real fs, isolated HOME, no mocks):
With |
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.
A full sync's skills orphan sweep no longer deletes directories the harness writes itself, starting with Claude Code's
skills/syncedbucket.Why
ebce589 stopped flattening plugin skills into Claude's top-level
skills/, which is right. It is tagged in v1.22.118, but npmlatestis still 1.22.117, so every Claude home on zion still carries the copies. Each home has 48 flattened plugin skills:animatornext tocreate:animator,debugnext toswarm:debug, and so on. All of them date from the 2026-09-23 sync.The cleanup is a full sync, but its sweep (
versions.ts) removes every non-dot directory underskills/that isn't in the current sync set. On Claude that includesskills/synced/, which Claude Code writes itself. Publishing 1.22.118 as-is and running the cleanup would have deleted it.Change
The agent spec names the directories a harness owns:
ownedSkillDirs, set to['synced']for Claude. The sweep trusts them alongside the synced skills. Everything else it swept before, it still sweeps.The first version of this PR gated the sweep on the previous manifest's
writtenTargetsinstead. The independent review rejected that, because a directory no sync ever recorded (any stale skill on an existing install) would silently never be cleaned. This version keeps that coverage.agents prune cleanup skillsneeds no change.listSkillsInVersionHome(plugins/skills.ts:496) only lists directories with aSKILL.mdat their root, andsyncedhas none.Evidence
This branch's build, run through
syncResourcesToVersion('claude', '2.1.260', undefined, { force: true })against a copy of the real zion claude@2.1.260 home. The copy sat in a temp HOME whose source repos were symlinks to the real ones; the real repos' git status is identical before and after. The rerun on the reworked commit gives the identical result:synced(Claude Code's own),learnandrun(real.agents-systemskills that also exist in plugins), and the other 14 repo-owned skills.Tests:
versions.test.ts"sweeps a flattened plugin skill…" now assertsskills/synced/bucketsurvives, while the untrackedold-shadowdirectory is still swept with no manifest present. WithownedSkillDirsremoved from Claude's spec it fails (expected false to be true); with it, it passes.testjob passes on this commit.New surface
AgentConfig.ownedSkillDirs(types.ts), a new optional agent-spec field. I tried to extendnativePluginSkillsandnativeAgentsSkillsDir, but both are booleans about where skills come from, not which directories underskills/belong to the harness. A name list is the only way to exemptsyncedwithout exempting everything, and it is the reviewer's suggested shape.Follow-up in this delivery
Release 1.22.119. mac-mini has been offline since 2026-09-07, so the release runs with
--device yosemite-s1, which holds the npmjs.com bundle. After the release,agents sync claude@all --forcecleans the affected homes; the owner authorized the deletion.🤖 Generated with Claude Code