stable-which
English | 日本語
Evaluate binary path stability and find stable PATH candidates.
Problem
Package managers and version managers place binaries in versioned or volatile directories:
/opt/homebrew/Cellar/jj/0.24.0/bin/jj # Homebrew Cellar (version-specific)
~/.local/share/mise/installs/node/22.0.0/bin/node # mise installs
./target/release/myapp # Cargo build output
After brew upgrade, the Cellar path breaks. When mise switches versions, the installs path changes. Build outputs move with every rebuild. Meanwhile, stable symlinks or shims exist on PATH:
/opt/homebrew/bin/jj -> ../Cellar/jj/0.24.0/bin/jj
~/.local/share/mise/shims/node
which finds a command by name but does not verify that the result points to the same binary. If multiple versions coexist, which may return a completely different binary.
stable-which enumerates all same-name candidates on PATH, tags each with stability properties, and scores them to find the most stable path that points to the same file.
How it works
- Canonicalize the input binary path
- Search PATH for all same-name binaries
- Tag each candidate (SameCanonical, InPathEnv, ManagedBy, BuildOutput, Ephemeral, etc.)
- Judge each candidate's durability (whether the path can be pinned into a service definition; see below)
- Rank candidates by the selected policy
- Return the best candidate (or all candidates with
--all)
Usage
CLI
# Best stable path (default: path format)
# /opt/homebrew/bin/jj
# Command name lookup
# /opt/homebrew/bin/jj
# All candidates as JSON
# Inspect all candidates as JSON (shorthand for --all --format json)
# Prefer path stability over binary identity
Library
Add the dependency:
// Keep this example in sync with the crate-root doc example in
// crates/stable-which/src/lib.rs (the canonical, doctest-verified version).
use ;
use Path;
// 1. Discover candidates (discovery only, deterministic PATH order)
let mut candidates = find_candidates?;
// 2. Rank them in place by a scoring policy
rank_candidates;
// 3. Inspect via accessors (fields are private)
for c in &candidates
let best = &candidates;
if best.durability == Durable
resolve_stable_path(binary, policy) is a convenience that composes
find_candidates + rank_candidates and returns the single best candidate.
CLI Options
stable-which [OPTIONS] <binary>
Arguments:
<binary> Path to the binary, or a command name to look up in PATH
Options:
--all Show all candidates (default: best candidate only)
--format <F> Output format: path (default), json
--policy <P> Scoring policy: same-binary (default), stable
--inspect Show all candidates as JSON (same as --all --format json)
-q, --quiet Suppress warnings
--help Show this help message
--version Show version
Scoring Policies
| Policy | Priority | Use case |
|---|---|---|
| same-binary (default) | Binary identity > Path stability | Service registration |
| stable | Path stability > Binary identity | Config files that survive upgrades |
Path Tags
Tags describe properties of each candidate path:
Positive (green): Input, InPathEnv, SymlinkTo, SameCanonical, SameContent
Warning (orange): ManagedBy, Shim, BuildOutput, Ephemeral, Relative, NonNormalized
Negative (red): DifferentBinary
Durability
Each candidate is also judged on an orthogonal durability axis (durable /
not-durable / unknown), exposed as Candidate::durability() and the
is_stable() convenience (true only for durable). This answers "can this
path be baked into a launchd plist / systemd unit and survive upgrade and
reboot?":
- durable: environment-wide reference surfaces (
/usr/bin,/opt/homebrew/bin, profile bins, standard shim dirs) - not-durable: versioned installs (
Cellar/,nix/store/,installs/), ephemeral / build-output / project-local paths - unknown: unrecognized locations and user dropboxes (
~/bin,~/.local/bin) — treated as not safe to pin (safe side)
Durability is judged per candidate, so the reference path /opt/homebrew/bin/git
is durable while its canonical realpath /opt/homebrew/Cellar/git/2.44.0/bin/git
is not-durable. The JSON output (--inspect) includes a durability field per
candidate.
Install
Or build from source:
License
MIT