CLI · commit ba2943a

errexplain

Explain failing commands & tracebacks with the source in context

Download errexplain.zip
Quickstart
unzip errexplain.zip && cd errexplain
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

errexplain

ee runs a command (or reads piped output), and when it fails it pulls out the error, reads only the source lines that matter from your disk, and asks Claude Haiku 5.5 for a short root cause and a concrete fix, as a unified diff when the model is confident. Most of the work (parsing, context selection, budgeting, redaction) is deterministic Python, so a request is typically a few hundred to ~3k input tokens, well under a cent.

Features

Install

cd errexplain
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
export PATH="$PWD/.venv/bin:$PATH"     # provides `ee` and `errexplain`
export ANTHROPIC_API_KEY=sk-ant-...    # or log in with `ant auth login`

Quickstart

ee -- pytest -x                 # run, and explain if it fails
make 2>&1 | ee                  # explain piped output
ee --dry-run -- python3 examples/pyapp/report.py   # no key needed
ee --apply -- go build ./...    # offer to apply the suggested patch

Usage

ee [OPTIONS] [COMMAND]...

  --model TEXT      Model id (env EE_MODEL)  [default: claude-haiku-5-5]
  --dry-run         Print what would be sent and the token estimate; no API call.
  -v, --verbose     Show token usage and estimated cost.
  --apply           Apply the suggested patch (git apply --check first, asks to confirm).
  -y, --yes         Do not ask for confirmation with --apply.
  --no-cache        Ignore and do not write the response cache.
  --root DIRECTORY  Repo root; files outside it are never sent.  [default: .]
  --budget INTEGER  Approximate token budget for source context.  [default: 3000]

Flags that belong to the wrapped command are passed through (ee pytest -x -k foo); use -- if it clashes with an ee flag. A command that exits 0 is never analysed and never costs anything.

Example: dry run on a real failure

examples/pyapp/report.py looks up a SKU that is missing from prices.json. This is real output (system prompt omitted for brevity):

$ cd examples/pyapp && ee --dry-run -- python3 report.py
Traceback (most recent call last):
  ...
KeyError: 'C300'

ee dry run: model=claude-haiku-5-5, model not called
--- user ---
Command: python3 report.py

Exit code: 1

Detected: python / KeyError

Environment:
- OS: Linux 6.8.0-138-generic
- python: Python 3.12.3

Error output:
...
Source inventory.py lines 1-13:
 9| def total_cost(items, prices):
10|     total = 0
11|     for item in items:
12|         total += prices[item["sku"]] * item["qty"]
13|     return total

Source report.py lines 1-12:
...
---
estimated input tokens: ~519 (~$0.000052)

The other samples in examples/ (Go panic, Node JSON.parse, Rust lifetime error, C undeclared identifier, a failing pytest; tsapp needs tsc, which was not available here) all work the same way, e.g. cd examples/goapp && ee --dry-run -- go run ..

Example: rendered answer (requires a real key)

The answer layout below was rendered from a stub response (the tests use a fake client); with a key the content comes from Haiku.

╭───────────────────────────────── errexplain ─────────────────────────────────╮
│ KeyError: no price for SKU C300.                                             │
│                                                                              │
│ Root cause                                                                   │
│ ITEMS in report.py contains C300, but prices.json only defines A100 and      │
│ B200, so the lookup in total_cost raises KeyError.                           │
│                                                                              │
│ Fix                                                                          │
│ 1. Add C300 to prices.json, or                                               │
│ 2. Validate SKUs in total_cost and fail with a clear message.                │
╰────────────────────────────── confidence: high ──────────────────────────────╯
--- a/inventory.py
...
tokens: 1000 in / 200 out, est. cost $0.000200

Configuration

Setting Purpose
ANTHROPIC_API_KEY Credentials (never logged; there is no --api-key flag).
EE_MODEL Default model id (claude-haiku-5-5).
EE_CACHE_DIR Cache location (default ~/.cache/errexplain).
.eeignore In the repo root; one glob or directory per line, # comments. Matching files are never read or sent.

Architecture

Module Role
runner.py Subprocess runner with tee, 400-line tail, exit codes (126/127 for launch failures); stdin reader.
parsers.py Per-language error extraction into ErrorInfo (message, excerpt, frames ordered most relevant first).
context.py Frame resolution, root and .eeignore enforcement, function spans, merging, token budget, environment facts.
redact.py Secret masking applied to the whole prompt.
llm.py Prompt builder, Explanation schema, messages.parse call, disk cache, error mapping, cost.
patch.py git apply --check / apply with path-escape protection.
render.py rich panel and diff.
cli.py click command wiring.

See docs/design.md for the algorithms.

Tests

.venv/bin/python -m pytest -q     # 88 tests, no network or API key needed

Parsers run against real captured outputs in tests/fixtures/; the model is replaced by a fake client.

Limitations