_ _ | |__ _ _ ___| |__ | '_ \| | | / __| '_ \ | | | | |_| \__ \ | | | |_| |_|\__,_|___/_| |_|
Context-efficient command runner for coding agents.
Wraps any shell command and prints a single ✓/✗ summary line. On success, shows only the summary. On failure, shows filtered output. Preserves exit codes.
go install github.com/alfranz/hush/cmd/hush@latest
Coding agents spend context tokens on verbose test output, even when all tests pass. hush reduces a passing run to a single line.
===== test session starts =====
platform linux -- Python 3.12.15
rootdir: /app
collected 39 items
tests/test_cart.py ............. [ 33%]
tests/test_discounts.py .......... [ 66%]
tests/test_pricing.py ............. [100%]
====== 39 passed in 0.03s ======
✓ pytest
| Passing tests | Without hush | With hush | Saved |
|---|---|---|---|
| pytest (39 tests) | 144 tokens | 6 tokens | 96% |
| Jest via npm test (27 tests) | 127 tokens | 6 tokens | 95% |
| go test (5 packages) | 98 tokens | 8 tokens | 92% |
| cargo test (27 tests) | 604 tokens | 7 tokens | 99% |
Measured with Claude's tokenizer on small real projects. Larger test suites print more, so they save more. On a failure, hush shows the full output by default. The only extra cost is the ✗ line, about 6–9 tokens. See the benchmark results for all numbers and outputs.
# Wrap any command
hush "pytest -x"
✓ pytest -x
# On failure, shows full output (traceback, assertion details, etc.)
hush "pytest -x"
✗ pytest -x
=================================== FAILURES ===================================
__________________________________ test_login __________________________________
def test_login():
response = client.get("/auth/login")
> assert response.status_code == 200
E AssertionError: assert 401 == 200
tests/test_auth.py:10: AssertionError
=========================== short test summary info ============================
FAILED tests/test_auth.py::test_login - AssertionError: assert 401 == 200
============================== 1 failed in 0.06s ===============================
One quoted argument runs with sh -c, so pipes, &&, ;, globs and $VARS work. Several arguments run the program directly, with the arguments as given. For simple commands, both forms give the same result.
# One quoted argument: runs with sh -c
hush "make check && make lint"
# Program and arguments: runs directly
hush go test -race ./...
✓ go test -race ./...
Put hush flags before the command. hush reads its own flags until the first argument that is not a flag. Everything after it belongs to the command. time, nice and env work the same way.
hush --tail 20 go test -race ./... # --tail is for hush, -race is for go
hush --label t -- go test ./... # -- ends the hush flags
hush "make check" --label check # error: flags after the command
By default, the label is the whole command, cut to 40 characters. So you can see which command failed. Use --label to set a shorter name.
hush "pytest tests/unit"
✓ pytest tests/unit
hush "pytest tests/integration"
✗ pytest tests/integration
# With --label
hush --label "unit" "pytest tests/unit"
✓ unit
# Last 30 lines of output
hush --tail 30 "npm test"
# Lines matching a pattern
hush --grep "error|FAIL" "make"
# First 20 lines
hush --head 20 "cargo build"
--grep and --tail are best suited for linters and build tools that produce high-volume output. For test runners (pytest, Jest, go test), prefer running without filters — the full traceback, assertion diffs, and source context are exactly what an agent needs to debug a failure. A --grep "FAIL" on pytest output, for example, strips away everything except the one-line summary.
Run multiple commands in sequence with a combined summary.
hush batch "ruff check ." "ty check src/" "pytest -x"
✓ ruff check .
✓ ty check src/
✓ pytest -x
✓ 3/3 checks passed
Each command is labeled with its own text. --label does not work with batch. To give checks names, use the config file.
Use --continue to run all commands even if one fails:
hush batch --continue "ruff check ." "false" "pytest -x"
✓ ruff check .
✗ false
✓ pytest -x
✗ 2/3 checks passed
Define named checks in .hush.yaml at your project root:
# .hush.yaml
defaults:
continue: true
checks:
lint:
cmd: ruff check .
types:
cmd: ty check src/
grep: "^error"
test:
cmd: pytest -x
tail: 40
Settings in defaults apply to all commands (root, batch, and named checks) unless overridden by per-check config or CLI flags. Precedence: CLI flags > per-check config > defaults.
Then run by name:
hush lint # run a single check
✓ ruff check .
hush all # run all checks in order
✓ ruff check .
✓ ty check src/
✓ pytest -x
✓ 3/3 checks passed
Filters stack: use --grep to find error lines, then --tail to limit how many are shown. This works well for builds and linters where each error line is self-contained. For test runners, prefer no filters or just --tail — the full traceback gives agents the context they need to fix failures.
hush --grep "error" --tail 10 "make build"
The default label is the whole command. When your agent runs the same test suite against different targets, short labels make the output easier to scan.
hush --label "unit" "pytest tests/unit"
hush --label "integration" "pytest tests/integration"
-x with pytestStop on first failure. There's no point burning tokens on 50 cascading failures when the first one is the root cause.
hush "pytest -x --tb=short"
--tb=short keeps pytest tracebacks concise, reducing the output hush needs to filter on failure.
Linters can produce huge output. Filter to just the actionable lines.
# Only show error lines for ty (they start with error[rule])
hush --grep "^error" "ty check src/"
# Ruff is usually concise, but cap it anyway
hush --tail 20 "ruff check ."
Jest and Vitest are notoriously verbose. For test failures, prefer --tail over --grep — it preserves the traceback and assertion context that agents need to debug. Use --grep for linters like ESLint where each line is self-contained.
# Tail keeps the traceback + summary intact
hush --tail 30 "npx jest --no-coverage"
# ESLint: just errors (each line is self-contained, grep is fine)
hush --grep "error" "npx eslint src/"
Pass --no-coverage to Jest when running via hush. Coverage tables add hundreds of tokens with zero debugging value.
hush --grep "error TS" "npx tsc --noEmit"
go test with targeted packagesGo test output is already fairly lean, but hush still saves tokens on pass. Target specific packages when possible.
hush "go test ./internal/runner/..."
hush "go test -race ./..."
hush "go vet ./..."
hush --grep "error" "staticcheck ./..."
Cargo is extremely verbose during compilation. Use --head or --grep to surface just the errors.
# First 20 lines catches the first compiler error
hush --head 20 "cargo build"
# Just errors from cargo test
hush --grep "FAILED|error\\[" "cargo test"
hush --grep "warning:|error:" "cargo clippy -- -W clippy::all"
These patterns work well with coding agents (Claude Code, Cursor, Aider, etc.).
Define your project's checks once, then the agent can run them all with a single command. This is not a git hook — the agent runs it explicitly.
# .hush.yaml
defaults:
continue: true
checks:
lint:
cmd: ruff check .
types:
cmd: ty check src/
grep: "^error"
test:
cmd: pytest -x --tb=short
tail: 40
# Agent runs:
hush all
Run lint, type-check, and tests in one batch. The agent sees a clean summary or exactly which step failed.
hush batch --continue \
"ruff check ." \
"ty check src/" \
"pytest -x --tb=short"
✓ ruff check .
✓ ty check src/
✓ pytest -x --tb=short
✓ 3/3 checks passed
Catch build errors early. Use --head to show the first error without the full cascade.
hush --head 15 "cargo build"
hush --head 15 "go build ./..."
hush --head 15 "npm run build"
When the agent edits a specific file, run only the related tests. Faster feedback, fewer tokens.
# Python: run the test file that matches
hush "pytest tests/test_auth.py -x"
# Go: run the specific package
hush "go test ./internal/runner/"
# JS: use Jest's path filter
hush "npx jest auth.test.ts"
Add the snippet below to your CLAUDE.md or AGENTS.md. The key point: agents will naturally reach for bare commands like pytest tests/test_foo.py::test_name — the rule needs to cover individual test runs, not just full suites.
## Running shell commands
Always wrap test, lint, and build commands in `hush` — including
targeted runs against a single file or test function:
# Tests — full suite or individual file / function
hush "pytest -x --tb=short"
hush "pytest tests/test_auth.py -x"
hush "pytest tests/test_auth.py::test_login -x"
hush "go test ./..."
hush "go test ./internal/runner/ -run TestRun"
hush "npx jest auth.test.ts --no-coverage"
# Linters and type checkers
hush "ruff check ."
hush "ty check src/"
# If .hush.yaml is present
hush all
Never run these bare — hush keeps output concise on success and
surfaces only the relevant lines on failure.
Don't use hush for commands where you need the full output (e.g., hush "cat file.txt" or hush "git diff"). hush is for commands where success = silence.
hush flags go before the command. See flag placement.
| Flag | Description |
|---|---|
--label | Custom label for the summary line. The default label is the command, cut to 40 characters. Not supported with batch |
--tail N | Show only last N lines on failure |
--head N | Show only first N lines on failure |
--grep PATTERN | Filter output to lines matching pattern |
--warn-pattern REGEX | On success, match warning lines and print ⚠ with details |
--warn-tail N | On a success with warnings, show the last N warning lines (default: 10) |
--continue | Continue running after a failure (batch/all) |
--version, -v | Print the hush version |
go install github.com/alfranz/hush/cmd/hush@latest
This places the binary in $GOPATH/bin (usually ~/go/bin). Make sure it's in your $PATH:
# Check if it's already there
which hush
# If not, add to your shell profile (~/.zshrc, ~/.bashrc, etc.)
export PATH="$HOME/go/bin:$PATH"