-
Notifications
You must be signed in to change notification settings - Fork 30
Description
Describe the bug
The tidy() method in src/update.ts has swapped arguments in the call to isNotSpecial, causing it to return true for every entry, including bin/ and current. This means the only protection against deletion is the 42-day age check (isOld), and any entry older than 42 days is deleted regardless of whether it should be preserved.
Root Cause
The function signature is:
const isNotSpecial = (fPath: string, version: string): boolean =>
!['bin', 'current', version].includes(basename(fPath))But the call site passes the arguments in the wrong order:
.filter((f) => isNotSpecial(this.config.version, f.path) && isOld(f.stat))This means:
- fPath receives this.config.version (e.g., "2.0.1") — basename("2.0.1") is "2.0.1", which never matches 'bin' or 'current'
- version receives f.path (a full filesystem path like /home/user/.local/share/myapp/client/bin) — this goes into the array but never matches basename("2.0.1")
So isNotSpecial always returns true, and nothing is treated as special.
Impact
After 42 days, tidy() deletes the bin/ directory, which breaks the CLI:
$ which myapp
myapp not foundThe bin/ directory's mtime is not updated by createBin() because writeFile on an existing file inside the directory doesn't change the directory's mtime, only creating or deleting files does. So even though createBin() runs before tidy() on each update, the bin/ directory retains its original mtime from installation and eventually exceeds the 42-day threshold.
The current symlink and versioned directories may also be deleted once they exceed 42 days, progressively degrading the installation.
To Reproduce
Steps to reproduce the behavior:
- Install a CLI that uses @oclif/plugin-update
- Wait 42+ days (or backdate the client/bin/ directory mtime for testing) ->
touch -t $(date -v-43d +%Y%m%d%H%M.%S) ~/.local/share/myapp/client/bin - Trigger an update (myapp update or via autoupdate)
- Observe that
client/bin/is deleted by tidy()
Expected behavior
The call should be:
.filter((f) => isNotSpecial(f.path, this.config.version) && isOld(f.stat))This would correctly preserve bin/, current, and the active version directory, only deleting old version directories.
Environment (please complete the following information):
- OS & version: MacOS Tahoe
- @oclif/plugin-update: 4.7.22