A barrier across a stream that's also the standard instrument for measuring its flow rate.
weir is a Claude Code addon that closes a specific gap: the model writes shell pipelines fluently, but reaches for a 1995 toolbox.
Mining one user's JSONL session transcripts across dozens of project directories (the sanitized aggregate is in internal/measure/baseline.json):
- 25,216 Bash invocations
- 49.7% contain a top-level pipe — the model writes pipelines constantly
- 0 of those calls reached for
rg,fd,bat,sd,mlr,eza, or any other modern alternative — not "underused", never reached for - A handful of antipatterns recur in volume — counts from the embedded baseline:
grep | head(810),ls | grep(225), UUOC variantscat FILE | tool(~120 across head/grep/tail/jq/wc),grep | wc -l(99),awk | awk(38),find ... -exec ... \;(13). A separate pass-A discovery on the same corpus (data/mine_extra.py) addedwhich CMD(~179),find | xargswithout-print0(~61), andls | wc -l(~152).
The model's pipeline shapes are fine. What's broken is which binaries it reaches for inside each shape, and a small cluster of stereotyped antipatterns.
Three layers, registered as Claude Code hooks. All fail-open; bypass with WEIR_SUGGEST_SKIP=1 / WEIR_SKIP=1.
At the start of every session, weir probes $PATH for ~30 modern shell tools, emits a JSON manifest, and injects it into the model's context as additionalContext. The model now knows what's reachable on this host instead of defaulting to coreutils.
sd is deliberately not in this catalog. It was recommended over sed through v0.1.11, but three field-reported sightings of the same replacement-string footgun — the third one a detection bug in the block rule meant to have already closed it — settled the question: the failure mode recurs faster than the rule table can be hardened against it. weir stops endorsing it; internal/suggest's six sd-specific rules stay fully active as a backstop, since a model can still reach for sd from its own training knowledge regardless of what weir recommends.
Sample output on a freshly-stocked Ubuntu 26.04:
[weir] Modern shell tools on this host:
- bat (prefer over cat) -> /usr/bin/batcat
- delta (prefer over diff) -> /usr/bin/delta
- fd (prefer over find) -> /usr/bin/fdfind
- jq (additive) -> /usr/bin/jq
- mlr (prefer over awk) -> /usr/bin/mlr
- rg (prefer over grep) -> /usr/bin/rg
...
When tools are missing but apt-installable, weir surfaces a sudo apt install ... line at install time and in weir status.
Before each Bash invocation, weir lints the command against the rule table in internal/suggest/rules.go. Two modes:
- Advisory (default): the suggestion is injected as context; the command still runs. Used for rules whose rewrites have edge cases.
- Block: weir refuses to run the command and shows the suggested rewrite; the model retries with the better form. Used only for mechanically-safe rewrites where the rewrite is lossless and unambiguous.
Currently block: uuoc (cat FILE | tool → tool FILE) and which-vs-command-v (which X → command -v X). Everything else stays advisory.
$ which python3
[weir-suggest] blocking — antipattern with a mechanical-safe rewrite:
- [which-vs-command-v] `which CMD` -> `command -v CMD`. `which` is non-POSIX
with inconsistent cross-distro behavior — can't see shell functions/aliases,
exit codes vary. `command -v` is POSIX, sees functions/aliases, exits
non-zero cleanly when missing. Rewrite the command and retry.
(To bypass this block for the rest of the session, set WEIR_SUGGEST_SKIP=1 in the env.)
After the manifest, weir injects three more blocks:
- Per-tool idioms from a parsed tldr-pages corpus — the first 2 examples per installed modern tool.
- Composition idioms — 30 hand-curated goal-shaped pipelines (find files modified today and grep them; pretty-print every JSON in a tree; benchmark two command variants). Filtered to entries whose required tools are all present on the host; capped at 10 surfaced per session.
- Silent-failure gotchas — classic-tool habits that fail quiet in the modern replacement (
rg -rsilently rewriting matches instead of recursing;sdwriting in place with no-i; a pipe's exit status being the last stage's, not the command that actually failed). Each entry is terse by design — mechanism, one failure shape, the fix — because the full incident-level detail lives in the matching layer-2 rule'sFixtext instead, paid for only on the turn the risky command is actually typed. A gotcha drops out of this list once a project's own Bash history clears a minimum volume of calls and shows its matching rule has never fired there; the layer-2 rule itself keeps firing regardless.WEIR_GOTCHAS=always/never/autooverrides the computed choice, and a muted entry always leaves a one-line count behind rather than vanishing silently.
Layer 3 teaches "how to compose" and "what to watch for"; layer 1 teaches "what's installed."
Requires Go 1.26 or later.
go install github.com/justinstimatze/weir@latest
weir installweir install does a non-destructive merge into ~/.claude/settings.json — it backs up first, only adds its two hook entries (SessionStart + PreToolUse:Bash), and never touches other hooks. Re-running is idempotent; weir uninstall is clean.
If you want it scoped to one project instead of globally, point at a project settings file:
CLAUDE_SETTINGS=/path/to/project/.claude/settings.json weir installBuilds and runs there — go install and Homebrew both work, and the
darwin/amd64/darwin/arm64 binaries weir install pulls come from the
same release as Linux's. Two things are worth knowing before relying on
it heavily:
- Rule correctness on macOS's default BSD userland —
grep/sed/find/ps/awk, a different implementation from the GNU tools this project's own corpus was mined against — gets checked once per tagged release, in.github/workflows/release.yml'smacos-checkjob. One BSD-specific landmine already has a rule (bsd-sed-i-mandatory-arg, gated to fire only on macOS);ps/awkhaven't been audited against BSD behavior yet. - The layer-1 capability manifest resolves
fd/batunder their Homebrew names directly — no macOS-specific code needed, since the Debianfdfind/batcatrename this project accounts for never existed on Homebrew in the first place.
| command | what it does |
|---|---|
weir suggest |
PreToolUse hook entry point — reads JSON on stdin, emits suggestion JSON |
weir inject |
SessionStart hook entry point — renders manifest + idioms + gotchas |
weir probe |
emit the capability manifest JSON for the current host |
weir install |
register weir's hooks; idempotent; backs up settings first |
weir uninstall |
remove weir's hooks; leaves unrelated hooks untouched |
weir status |
report which weir hooks are registered + suggest missing-apt tools |
weir measure |
re-mine ~/.claude/projects/**/*.jsonl and diff against the embedded baseline (modern-vs-classic tool counts, per-rule hit counts) |
weir review CMD |
interactive spot-check — given a bash command, print whether weir would block, advise, or stay silent (use for rule tuning) |
weir build-idioms |
maintainer-only: parse a tldr-pages clone into internal/idioms/idioms.json (then rebuild the binary to re-embed) |
- This isn't valuable for every project, and that's measured, not a guess. Rule-fire density (how often a project's own Bash history ever touches a hazard weir warns about) ranged from 1.3% to 20.7% across 85 real projects — a project that's mostly
git/npm/docker/test-runner invocations will rarely if ever hit what weir is for, while one built aroundrg/sd/fd/pipelines will hit it constantly. Layer 3's gotcha muting adapts to this per project, but only for the gotcha section specifically — the manifest and idiom blocks still render regardless of density. If none of it earns its keep on a given project,WEIR_SKIP=1turns SessionStart injection off entirely. - Fail-open everywhere. Any error in the hook → silent exit 0, command runs. weir cannot break your session.
- Block mode is conservative. Only on rewrites where the substitution is mechanically lossless. Edge-case rules stay advisory.
- Bypass is one env var per layer.
WEIR_SUGGEST_SKIP=1disables suggest output for the session;WEIR_SKIP=1disables the SessionStart inject entirely;WEIR_GOTCHAS=always/neveroverrides just the gotcha-muting decision without touching either of the other two. - Suggestions are concise. Rule fix-text is one paragraph max. SessionStart injection is bounded: the per-tool tldr idiom section caps at 2000 chars (~500 tokens), the cross-tool composition section caps at 1500 chars (~375 tokens), the gotcha section caps at 3000 chars (~750 tokens) and shrinks further per-project via the muting above, and the manifest itself scales with installed-tool count. Worst case with every budget maxed is under 2000 tokens.
- Settings.json edits are non-destructive + reversible. Every write backs up first to
<path>.weir-bak-<timestamp>; uninstall removes only weir-owned entries.
Single self-contained binary (~2.6 MB, stripped). All runtime data (idiom corpus, baseline snapshot for weir measure) is embedded via //go:embed — no source-tree dependency at runtime.
main.go # subcommand dispatch + --version
internal/
probe/ # PATH discovery + apt-pkg mapping (+ symlink dedup)
suggest/ # rule table + match engine + selftest + review
inject/ # SessionStart prose renderer (manifest + idioms + composition + gotchas)
idioms/ # per-tool (tldr) + composition idioms; build-idioms parser
install/ # non-destructive settings.json merge + status + uninstall
measure/ # corpus streamer + baseline diff (embedded baseline.json)
rulehistory/ # per-project rule-fire history cache; gates gotcha muting
guard/ # panic-recover wrapper for all hook entry points
data/
baseline_2026-05-20.json # sanitized aggregate baseline (the empirical evidence)
mine_pipes.py # the original baseline-extraction script (Python, run rarely)
mine_extra.py # pass-A: hunt named antipatterns
mine_discover.py # pass-B: frequency-rank for unknowns
eval/ # synthetic eval harness (uv venv, prompts.jsonl, run/grade/report)
scripts/
smoke.sh # install/uninstall cycle test (CI runs on every push)
sanitize-baseline.py # PII-strip raw mining output for the public artifact
The compiler/dataflow lineage (PaSh, PaSh-JIT, Koala, Smoosh, POSH), surface-tooling lineage (ShellCheck, Ultimate Plumber), agent-side lineage (Warp, Butterfish, NL2SH, Terminal-Bench), and documentation lineage (tldr-pages, explainshell). Full citations + how each relates to weir in INFLUENCES.md.
Layer 4 (streaming/blocking classifier), eval extensions (block-and-retry simulation, cross-host probe), rule-table extensions, and distribution work. See ROADMAP.md. Historical dead ends in WHAT_DIDNT_WORK.md.
MIT. See LICENSE.