Commit Graph
17 Commits
Author SHA1 Message Date
Sergey Galuza 00a4dceb94 fix(shared-manager): publish adopted settings by replacement
Adoption moved the canonical settings.json aside with rename() and left
the path empty until publication, roughly 100 ms later. Claude Code or a
second `ccs` starting inside that window found no file and seeded an
empty placeholder; publication then failed with EEXIST because link() is
no-replace, and the rollback published a backup and unlinked the claim,
destroying the only remaining copy of the user's settings. Recovering
meant digging through sidecar files by hand.

Publish by replacement instead: write a temp file next to the canonical
inode and rename() it over the target, so the path always holds a regular
file and no placeholder can be seeded. A compare-and-swap guard on
(ino, mtime, size) runs immediately before the rename and refuses to
publish when the canonical inode changed since it was read, so a writer
that got there first is still never clobbered. The pre-image backup is
published before the replacement, keeping the old content recoverable if
publication is interrupted.

Drops the canonical claim entirely along with restoreCanonicalClaim, and
folds the two identical sidecar publishers into one helper.
recoverOrphanedCanonicalClaim stays, since claims written by older
versions may still be on disk.

New tests cover both writers seen in the incident: Claude Code seeding
`{}` with a trailing newline, and a second `ccs` seeding the 2-byte
variant from shared-dir-linker. Four tests that pinned the claim-based
design were rewritten, among them `preserves a canonical write that
lands during no-replace publication`, whose intent is now enforced by
the CAS guard instead of by an EEXIST from a no-replace link.

Built [OnSteroids](https://onsteroids.ai)
2026-08-23 08:32:26 +02:00
Tam Nhu Tran 7ae617f0ee fix(shared-manager): preserve diverged files safely 2026-08-02 20:21:09 -04:00
Nelson Melo fa6b27f159 fix(shared-manager): adopt diverged plugin registry files in linkInstancePlugins
The same atomic-rename divergence occurs in the plugins subtree: a
plugin install inside a session rewrites plugins/installed_plugins.json,
replacing the instance-level symlink with a regular file. The per-launch
relink then discarded it, so the plugin was effectively uninstalled on
every relaunch while settings.json still marked it enabled — sessions
then fail with 'Unknown skill: <plugin>:<skill>'.

Move adoptDivergedFileContent to fs-helpers (avoids a circular import)
and apply it to file-type plugin entries before re-linking.
2026-07-31 13:40:23 -04:00
Nelson Melo 481f013ec2 fix(shared-manager): adopt diverged settings.json content before re-linking
Claude Code saves settings.json atomically (temp file + rename), which
replaces the managed shared symlink with a regular file holding the
user's latest changes (see #57). The launch-time relink then deleted
that file without reading it, silently reverting plugin enables and any
other in-session settings change on every profile relaunch.

Adopt the diverged file's content into the canonical ~/.claude file
(with a .bak-ccs-adopt backup) before restoring the symlink, at both
the shared-level and instance-level reconciliation points.

Fixes #1681
2026-07-31 12:14:29 -04:00
Kai (Tam Nhu) Tran f44b651cd6 fix(management): skip broken shared plugin entries 2026-05-30 14:57:17 -04:00
Tam Nhu Tran 3330229686 refactor: extract plugin path normalizer 2026-04-29 14:42:39 -04:00
Tam Nhu Tran 2293368e31 fix(shared-manager): prune transient and malformed marketplace entries 2026-04-15 16:29:25 -04:00
xuhaodong 5b262f0139 fix(shared-manager): exclude .staging dirs from marketplace registry sync
Claude Code's marketplace auto-update uses a three-step atomic rename:
clone to <name>.staging → rename old dir to <name>.bak → rename staging
to the final name. On Windows, EPERM errors can interrupt the rename-dance
and leave a <name>.staging directory permanently on disk.

CCS scanned physical directories to discover marketplaces, so a lingering
.staging directory was registered as a bare { installLocation } entry in
known_marketplaces.json. Claude Code's Zod schema requires each entry to
also have `source` and `lastUpdated` fields, so the corrupt entry caused
the /plugin command to throw a validation error.

Fix (two layers of defence):

- discoverMarketplaceEntries: skip any directory whose name starts with
  '.' or ends with '.staging'. This filters all hidden dirs including
  .staging and .bak left behind by interrupted rename operations.

- buildMarketplaceRegistryContent: invert the loop direction. Instead of
  iterating discoveredEntries and adding to the registry (which could
  introduce bare entries for disk-only directories with no registry
  record), iterate the merged registry and only keep entries that also
  exist on disk. Disk-only directories are now silently ignored.

Adds two regression tests: one for .staging pollution, one for orphan
registry entries whose physical directory has been removed.
2026-04-15 15:02:54 +08:00
Tam Nhu Tran c923f51cf5 fix(shared-manager): guard inverse shared symlink loops 2026-03-24 14:38:27 -04:00
Tam Nhu Tran 05dea22238 fix(shared-manager): allow external claude symlink chains 2026-03-24 13:34:34 -04:00
Tam Nhu Tran fc02c4b968 fix(management): harden marketplace state transitions
- detach verified shared symlinks when a profile is reopened as bare

- prune stale marketplace entries whose payload directories no longer exist

- add regressions for bare/non-bare transitions and stale registry recovery
2026-03-18 07:55:09 -04:00
Tam Nhu Tran 68a5d17327 fix(management): serialize marketplace registry reconciliation
- keep bare and non-bare marketplace normalization inside the plugin-layout lock
- warn when malformed registry sources are skipped during reconciliation
- add regression coverage for cross-instance refresh metadata and legacy layout upgrade
2026-03-18 07:34:05 -04:00
Tam Nhu Tran 54ea36fd18 fix(management): localize marketplace registry per instance 2026-03-18 07:08:09 -04:00
Tam Nhu Tran 1043a433f6 fix: normalize plugin metadata across shared launch flows 2026-03-17 09:56:31 -04:00
Tam Nhu Tran 2cb5b4ebec fix(shared-manager): normalize marketplace registry paths 2026-03-17 09:56:31 -04:00
Tam Nhu Tran 539afea737 style: format source and test files 2026-02-17 17:03:11 +07:00
kaitranntt 1067afbea7 fix(shared-manager): normalize plugin registry paths to canonical ~/.claude/
Replaces instance-specific paths (/.ccs/instances/<name>/) with
canonical /.claude/ paths in installed_plugins.json after linking.

Fixes #276
2026-01-05 20:48:49 -05:00