Skip to content

tidy() deletes all old entries including bin/ due to swapped arguments in isNotSpecial #1289

@khaled4vokalz

Description

@khaled4vokalz

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 found

The 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:

  1. Install a CLI that uses @oclif/plugin-update
  2. 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
  3. Trigger an update (myapp update or via autoupdate)
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions