stable-which 0.3.3

Evaluate binary path stability and find stable PATH candidates
Documentation

stable-which

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

  1. Canonicalize the input binary path
  2. Search PATH for all same-name binaries
  3. Tag each candidate (SameCanonical, InPathEnv, ManagedBy, BuildOutput, Ephemeral, etc.)
  4. Score candidates based on the selected policy
  5. Return the best candidate (or all candidates with --all)

Usage

CLI

# Best stable path (default: path format)
stable-which /opt/homebrew/Cellar/jj/0.24.0/bin/jj
# /opt/homebrew/bin/jj

# Command name lookup
stable-which jj
# /opt/homebrew/bin/jj

# All candidates as JSON
stable-which --all --format json jj

# Inspect all candidates as JSON (shorthand for --all --format json)
stable-which --inspect jj

# Prefer path stability over binary identity
stable-which --policy stable ./target/release/myapp

Library

use stable_which::{find_candidates, ScoringPolicy};
use std::path::Path;

let candidates = find_candidates(Path::new("jj"), ScoringPolicy::SameBinary)?;
for c in &candidates {
    println!("{}: {:?}", c.path.display(), c.tags);
}

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)
    --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

Install

brew install kawaz/tap/stable-which

Or build from source:

cargo build --release -p stable-which-cli

License

MIT