Skip to content

Install paths are compared as strings, so one install spelled two ways reads as two installs #230

Description

@bman654

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions