Skip to content

fix(installer): .claude/agents is a shared namespace — stop wiping user subagents on init/update#151

Merged
pilotspacex-byte merged 2 commits into
mainfrom
fix/installer-agents-clobber
Jul 14, 2026
Merged

fix(installer): .claude/agents is a shared namespace — stop wiping user subagents on init/update#151
pilotspacex-byte merged 2 commits into
mainfrom
fix/installer-agents-clobber

Conversation

@pilotspacex-byte

Copy link
Copy Markdown
Contributor

User-reported data loss: every add init/update deleted ALL non-ADD subagents from .claude/agents.

Root cause: the MANAGED table routed the agents tree through the whole-dir clean-replace, whose "no orphan survives" contract is correct for ADD-owned trees but wrong for .claude/agents — the one managed destination that is a shared namespace root (the user's own Claude Code subagents live there).

Fix (both installers, npm + pip, byte-mirrored semantics): a shared-namespace lander — per shipped file, temp-sibling + atomic rename; removal is explicit-tombstone-only (RETIRED_AGENTS, empty today), never a sweep or name heuristic. A user file named add-anything.md survives. All other managed trees keep whole-dir clean-replace unchanged (pinned).

Superseded pin: test_roster_shipped.test_update_clean_replaces_agents demanded the sweep that WAS the bug — strengthened to assert survival + roster refresh.

Tests: NEW test_installer_shared_namespace.py (8, both installers via subprocess). Full fence 3562/3562 OK. Engine untouched. ADD fast-lane task installer-shared-namespace-guard, frozen v1, gate PASS.

…place it whole

User-reported data loss: every `add` init/update swept ALL non-ADD subagents
from .claude/agents. Root cause: the MANAGED table routed the agents tree
through the whole-dir clean-replace (stage-then-swap), whose contract —
"a file removed upstream leaves no orphan" — treats every undeclared file
as an orphan. Correct for the ADD-owned trees (.add/tooling, .add/docs,
.claude/skills/add, .add/personas-teacher); wrong for .claude/agents, the
one destination other tools and the USER also write.

- NEW shared-namespace lander in BOTH installers (npm bin/cli.js
  sharedFileReplace + pip _installer.py _shared_file_replace, byte-mirrored
  semantics): per shipped file, temp-sibling + atomic rename (crash never
  leaves a torn file); removal is EXPLICIT-tombstone-only
  (RETIRED_AGENTS/_RETIRED_AGENTS, empty today) — never a sweep, never a
  name heuristic (a user file named add-anything.md survives)
- routed via SHARED={"agents"} in the one reconcile loop each installer has
  (init AND update funnel through it); GLOBAL_TREES excludes agents in both
- roll-up shape {restored, refreshed} preserved for the reporting consumer
- test_roster_shipped's superseded pin ("update must clean-replace agents/")
  STRENGTHENED to the new truth: undeclared files survive, shipped files
  still refresh (the sweep it demanded WAS the data-loss bug)

Tests: NEW test_installer_shared_namespace.py (8: foreign-file survival
through init+update x both installers · prefixed foreign file survives ·
fresh dir + roster lands · tombstone removal · non-shared docs tree still
sweeps orphans · roll-up shape). Full fence 3562/3562 OK. Engine add.py
untouched — no MD5 re-aim.

refs: standalone fast task installer-shared-namespace-guard, gate PASS
author: Tin Dang <tindang.ht97@gmail.com>
…or roster-refresh gap (user agents safe, verified live)

author: Tin Dang <tindang.ht97@gmail.com>
@pilotspacex-byte pilotspacex-byte merged commit 941659d into main Jul 14, 2026
11 of 12 checks passed
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.

2 participants