Skip to content

fix(sync): the skills orphan sweep never deletes a harness's own skill dirs - #3712

Merged
muqsitnawaz merged 4 commits into
mainfrom
fix/bound-skills-orphan-sweep
Sep 26, 2026
Merged

muqsitnawaz merged 4 commits into
mainfrom
fix/bound-skills-orphan-sweep

Conversation

@muqsitnawaz

@muqsitnawaz muqsitnawaz commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

A full sync's skills orphan sweep no longer deletes directories the harness writes itself, starting with Claude Code's skills/synced bucket.

Why

ebce589 stopped flattening plugin skills into Claude's top-level skills/, which is right. It is tagged in v1.22.118, but npm latest is still 1.22.117, so every Claude home on zion still carries the copies. Each home has 48 flattened plugin skills: animator next to create:animator, debug next to swarm: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 under skills/ that isn't in the current sync set. On Claude that includes skills/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 writtenTargets instead. 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 skills needs no change. listSkillsInVersionHome (plugins/skills.ts:496) only lists directories with a SKILL.md at their root, and synced has 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:

real-data run

  • Removed: the 48 flattened plugin copies.
  • Kept: synced (Claude Code's own), learn and run (real .agents-system skills that also exist in plugins), and the other 14 repo-owned skills.
  • Commands: untouched. The 19 alias commands are intentional (.agents-system b790cdc).

Tests:

  • versions.test.ts "sweeps a flattened plugin skill…" now asserts skills/synced/bucket survives, while the untracked old-shadow directory is still swept with no manifest present. With ownedSkillDirs removed from Claude's spec it fails (expected false to be true); with it, it passes.
  • CI test job passes on this commit.

New surface

  • AgentConfig.ownedSkillDirs (types.ts), a new optional agent-spec field. I tried to extend nativePluginSkills and nativeAgentsSkillsDir, but both are booleans about where skills come from, not which directories under skills/ belong to the harness. A name list is the only way to exempt synced without 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 --force cleans the affected homes; the owner authorized the deletion.

🤖 Generated with Claude Code

muqsitnawaz and others added 4 commits September 25, 2026 17:36
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>
@muqsitnawaz muqsitnawaz changed the title fix(sync): the skills orphan sweep deletes only directories a sync wrote fix(sync): the skills orphan sweep never deletes a harness's own skill dirs Sep 26, 2026
@muqsitnawaz

Copy link
Copy Markdown
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 ownedSkillDirs: ['synced'] as the only exclusion (versions.ts:2847-2853).

Reproductions (real fs, isolated HOME, no mocks):

  • First full sync with no manifest: a pre-existing orphan is swept.
  • An untracked leftover is swept on the next full sync.
  • A codex command-skill orphan is swept once its source is removed.
  • Claude's synced/ survives two full syncs.

With ownedSkillDirs removed, the test fails, so the field matters. versions.test.ts passes 74 of 74, and the staleness writer tests pass 36 of 36. prune cleanup needs no change, because plugins/skills.ts:496 requires SKILL.md at the directory root.

@muqsitnawaz
muqsitnawaz merged commit 2da7918 into main Sep 26, 2026
8 checks passed
@muqsitnawaz
muqsitnawaz deleted the fix/bound-skills-orphan-sweep branch September 26, 2026 06:05
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.

1 participant