_ _ | |__ _ _ ___| |__ | '_ \| | | / __| '_ \ | | | | |_| \__ \ | | | |_| |_|\__,_|___/_| |_|
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 burn thousands of context tokens on verbose test output — even when all tests pass. hush reduces that to a single line.
===== test session starts =====
platform linux -- Python 3.12
...
tests/test_auth.py::test_login PASSED [ 2%]
tests/test_auth.py::test_logout PASSED [ 4%]
... (47 more lines)
====== 47 passed in 3.42s ======
✓ pytest
| Test Runner | Raw Output | With hush | Saved |
|---|---|---|---|
| pytest (47 tests) | ~1,028 tokens | ~4 tokens | 99% |
| Jest (89 tests) | ~886 tokens | ~5 tokens | 99% |
| go test (14 pkgs) | ~196 tokens | ~6 tokens | 96% |
| cargo test (51 tests) | ~683 tokens | ~5 tokens | 99% |
# Wrap any command
hush "pytest -x"
✓ pytest
# On failure, shows full output (traceback, assertion details, etc.)
hush "pytest -x"
✗ pytest
=================================== 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 ===============================
By default, hush derives the label from the command name. Use --label to override it — especially useful when running the same tool multiple times.
# Default: label is derived from command ("pytest")
hush "pytest tests/unit"
✓ pytest
hush "pytest tests/integration"
✗ pytest # which suite failed?
# With --label: easy to tell apart
hush --label "unit" "pytest tests/unit"
✓ unit
hush --label "integration" "pytest tests/integration"
✗ integration # clear
# 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
✓ ty
✓ pytest
✓ 3/3 checks passed
Use --continue to run all commands even if one fails:
hush batch --continue "ruff check ." "false" "pytest -x"
✓ ruff
✗ false
✓ pytest
✗ 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
hush all # run all checks in order
✓ ruff
✓ ty
✓ pytest
✓ 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"
When your agent runs the same test suite against different targets, labels make the output scannable.
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 lines with "error" for ty
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 2>&1"
# 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
✓ ty
✓ pytest
✓ 3/3 checks passed
Catch build errors early. Use --head to show the first error without the full cascade.
hush --head 15 "cargo build 2>&1"
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.
| Flag | Description |
|---|---|
--label | Custom label for the summary line |
--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 |
--continue | Continue running after a failure (batch/all) |
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"