fix(install): stop legacy commands cleanup from deleting itself on case-insensitive filesystems#219
Merged
Merged
Conversation
…se-insensitive filesystems install.sh's Commands step created the commands/Recall/*.md symlinks, then immediately deleted them: the legacy-directory cleanup compared "$CLAUDE_DIR/commands/recall" against "$CLAUDE_DIR/commands/Recall" with a plain string `!=`. On the default case-insensitive-but-case-preserving macOS filesystem those two paths resolve to the SAME directory, so the guard never tripped and `rm -rf` wiped out every symlink the loop above had just created. Every fresh install left every Recall:* slash command missing; re-running install.sh reproduced the identical create-then-delete cycle forever, and only `recall doctor --fix` (which has no such cleanup step) actually fixed it — until the next install.sh run undid it again. Fix: compare by device+inode (`-ef`) instead of by string, in both places this logic is duplicated (install.sh's inline Commands step and lib/install-lib.sh's recall_copy_runtime_files, used by update.sh). Added a regression test exercising recall_copy_runtime_files against a case-aliased legacy path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every fresh
./install.shrun left everyRecall:*slash command missing, and re-running the installer reproduced the exact same failure forever — onlyrecall doctor --fixactually worked, until the nextinstall.shrun undid it again.Screenshot reproduced:
Root cause
install.sh's Commands step creates thecommands/Recall/*.mdsymlinks, then a "legacy lowercase directory" cleanup runs right after to remove any leftovercommands/recall/from an older naming scheme:On the default case-insensitive-but-case-preserving macOS filesystem,
commands/recallandcommands/Recallare the same directory on disk. The plain string!=sees them as different paths, so the guard never trips —rm -rfdeletes the directory the loop directly above had just populated with symlinks.The identical block was duplicated in
lib/install-lib.sh'srecall_copy_runtime_files(used byupdate.sh), soupdate.shhad the same bug.recall doctor --fix"worked" only because its repair path has no such cleanup step — but the nextinstall.sh/update.shrun re-triggered the delete.Fix
Replace the string comparison with bash's
-efoperator (compares by device+inode, so it correctly identifies "same directory on disk" regardless of case-folding) in both duplicated locations.Testing
tests/install/update.test.tsexercisingrecall_copy_runtime_filesagainst a case-aliased legacy path.lib/install-lib.sh.install.sh --yesdry run (isolated sandbox$HOME): self-check passes, all 9 command symlinks survive two consecutive runs (matching the original bug's exact repro).install.shagainst my own live$HOME: cleared a stale.install-incompletemarker left over from hitting this exact bug earlier,recall doctorhealth score went from 26/30 to 27/30 with "Install completed (no interrupted run)" now passing.🤖 Generated with Claude Code