Find commands with multiple installs on your PATH. Flag the ones where versions disagree.
which -a tells you how many copies of a command exist. pathdrift tells you when two copies are different versions — the bug that makes a command behave one way in your shell and another way under cron.
Python 3.8+. Standard library only. One file.
You install a CLI tool. Later you install an updated version via a different path (user-level vs system-level, snap, brew, npm‑global, nvm). Your shell finds the new one first. Cron finds the old one first. They disagree on config schema, flag syntax, output format — something — and you spend an afternoon chasing a "flaky" bug that's actually two binaries.
Real example that motivated this:
[DRIFT ] openclaw
/home/me/.npm-global/bin/openclaw
version: 2026.4.15
/usr/local/bin/openclaw
version: 2026.3.28
Different binaries, different config validators, different error messages, same command name. which -a showed both but wouldn't tell me their versions were three weeks apart.
curl -fsSL https://raw.githubusercontent.com/kithfoss/pathdrift/main/pathdrift.py -o ~/.local/bin/pathdrift
chmod +x ~/.local/bin/pathdriftOr clone and symlink:
git clone https://github.com/kithfoss/pathdrift
ln -s "$(pwd)/pathdrift/pathdrift.py" ~/.local/bin/pathdrift# Scan everything on PATH. Print only the drift.
pathdrift
# Include commands with multiple installs at matching versions.
pathdrift --all
# Hide commands whose --version can't be probed.
pathdrift --quiet
# Check a single command.
pathdrift python3
# Simulate a different environment — e.g. cron's PATH.
pathdrift --path "/usr/bin:/bin"Exit codes:
0— no drift detected1— drift detected (or a named command wasn't found)2— PATH was empty
Two installs of the same command are drift when their first non-empty --version output contains different semver-ish numbers. Symlinks that resolve to the same underlying file are collapsed — those aren't drift, they're aliases.
Commands that don't answer --version (editors, pagers, random shell scripts) show as [unknown]. Hide them with --quiet.
which -a is truthful. But two installs at version 1.2.3 are harmless. Two installs at 1.2.3 and 2.0.0-rc1 are a future incident. pathdrift is the second thing — the one that tells you to care.
- Walk the PATH in order, de-duplicating directory entries.
- Build a map of command name → list of full paths.
- For each command with more than one path, run
<path> --version(or the right override) in a thread pool. - Collapse paths that resolve to the same file via
realpath. - Extract a semver-shaped number from each output. If the numbers differ — drift.
Timeouts default to 3 seconds per probe. Override with --timeout.
- Not a package manager. It tells you drift exists; fixing it is your call.
- Not a replacement for
update-alternatives— that's for managing, this is for noticing. - Not a system linter. It runs when you run it.
- No config file. If you need more than the flags offer, fork it.
MIT.