clodex patch and clodex patch --restore compare install paths with plain string equality —
manifest.binaryPath === binaryPath, and the install path inside a provenance record. One install
spelled two ways therefore reads as two installs.
This is not Windows-only. APFS is case-insensitive and the fs.realpathSync clodex uses
preserves caller-supplied case (only .native corrects it), so a hand-typed
TWEAKCC_CC_INSTALLATION_PATH reaches it on macOS. Reproduced.
What it costs today
Both consequences are safe directions, which is why #204 shipped without fixing this:
- A false refusal. With a provenance record written under spelling A, a restore under spelling B
reads the record as another install's and refuses instead of falling back. The message names the
recorded spelling, so following its own advice recovers.
- A manifest that outlives its install's restore.
--restore clears the manifest only when
manifest.binaryPath === binaryPath, so a manifest written under the other spelling survives a
successful restore. Stale rather than harmful — evaluatePatchState sees the hash mismatch and
re-patches — but it says the install is patched when it is not.
The fix, and why it is not a one-liner
One path-equivalence helper — exact string match first, then compare realpathSync.native() results
(or stat device+inode) while both paths exist — used consistently for record keys, selection,
manifest matching, and manifest cleanup.
realpathSync.native would fix the root cause, but it changes the spelling clodex resolves to, which
invalidates the binaryPath in every existing manifest. That needs a migration: on read, accept a
manifest whose binaryPath differs only by case/link resolution and rewrite it. Without the
migration, every user's first run after upgrading looks like "the manifest records a different
install" and refuses.
Raised during the #204 review; the behaviour is documented under "Pristine backups are
content-addressed" in .claude/docs/patcher.md.
clodex patchandclodex patch --restorecompare install paths with plain string equality —manifest.binaryPath === binaryPath, and the install path inside a provenance record. One installspelled two ways therefore reads as two installs.
This is not Windows-only. APFS is case-insensitive and the
fs.realpathSyncclodex usespreserves caller-supplied case (only
.nativecorrects it), so a hand-typedTWEAKCC_CC_INSTALLATION_PATHreaches it on macOS. Reproduced.What it costs today
Both consequences are safe directions, which is why #204 shipped without fixing this:
reads the record as another install's and refuses instead of falling back. The message names the
recorded spelling, so following its own advice recovers.
--restoreclears the manifest only whenmanifest.binaryPath === binaryPath, so a manifest written under the other spelling survives asuccessful restore. Stale rather than harmful —
evaluatePatchStatesees the hash mismatch andre-patches — but it says the install is patched when it is not.
The fix, and why it is not a one-liner
One path-equivalence helper — exact string match first, then compare
realpathSync.native()results(or
statdevice+inode) while both paths exist — used consistently for record keys, selection,manifest matching, and manifest cleanup.
realpathSync.nativewould fix the root cause, but it changes the spelling clodex resolves to, whichinvalidates the
binaryPathin every existing manifest. That needs a migration: on read, accept amanifest whose
binaryPathdiffers only by case/link resolution and rewrite it. Without themigration, every user's first run after upgrading looks like "the manifest records a different
install" and refuses.
Raised during the #204 review; the behaviour is documented under "Pristine backups are
content-addressed" in
.claude/docs/patcher.md.