ExitZero
AI-powered CLI that diagnoses failing logs, explains the root cause, and proposes safe fixes.
ExitZero reads your test, build or runtime logs, pulls the broken source code from disk, and sends everything to a cloud LLM via OpenRouter. The model returns a structured JSON diagnosis, and that JSON (not free-form chat) drives the system's decisions: whether to explain, ask, or apply a fix.
pytest 2>&1 | tee error.log
python -m exitzero --file error.log
Dependency Error caught with confidence score of High
Line 3 of tests/test_api.py
What went wrong
The Python module 'httpx' is not installed in the environment where
the test is running.
How to fix it
Install the httpx package by running `pip install httpx`, then
re-run the test.
Table of Contents
- How AI is Used
- Features
- Architecture
- Tech Stack
- Quick Start
- Usage
- Try the Demo Folder
- Configuration
- Testing
- AI Usage Disclosure
- Limitations
- Future Improvements
How AI is Used
ExitZero uses AI as the reasoning core, not as a chatbot.
1. Source-aware reasoning The model receives the raw error, the broken source code pulled from disk, and the git diff for the file. It reads your actual code, not just the error message.
2. Agentic investigation (slow path)
When no source snippet is available, the model enters a tool loop. It can call read_file, search_codebase, find_symbol, get_git_diff, get_git_status and inspect_dependencies to gather evidence before concluding. The loop is capped at 8 iterations and 6 tool calls.
3. Structured output The model must respond with a strict JSON schema:
{
"done": true,
"category": "Dependency Error | Real Bug | Environment Issue | Network Error | ...",
"confidence": "High | Medium | Low",
"plain_english": "what went wrong in the user's code",
"fix_explanation": "concrete steps to fix it",
"patch_diff": "unified diff or null",
"shell_fix": "safe shell command or null"
}
This JSON is parsed (with repair for malformed output), validated, and turned into a Diagnosis dataclass. The Diagnosis drives the confidence gate, patch application, command execution and UI rendering.
4. Confidence-gated action
A deterministic gate in llm/confidence.py decides what happens next:
Highconfidence: auto-apply, but only when the diagnosis is verified or supported and has evidence from an observed file read (or has no patch to apply).Mediumconfidence: ask the user before doing anything.Lowconfidence, unknown category, unverified diagnosis, or no fix proposed: explain only, apply nothing.
5. Safety layer
Even when the model proposes a fix, nothing runs directly. Shell commands must pass a deny-list (rm -rf, curl | bash, eval, etc.) and an allow-list (pytest, pip install, npm test, etc.). Patches go through git apply --check before git apply, and are reverted if validation fails.
Features
- Multi-language: Python, JavaScript, TypeScript, React, Node.js, Rust, Go, Java, Shell
- Build tool errors: Vite, esbuild, tsc, webpack
- Dependency errors: npm ENOENT and ERESOLVE, pip ImportError, missing packages
- Network errors: connection timeouts, SSL, ENOTFOUND, fetch failures
- Source code aware: pulls the broken lines from disk and sends them to the model
- Progress indicator: a spinner while the model works; raw JSON never leaks to the terminal
- Safe remediation: deny-list, allow-list, two-phase patching and automatic revert
- JSON repair: recovers malformed model output (single quotes, truncated JSON, bare keys)
- Retry with backoff: handles transient API failures
- Model failover: tries a primary model, then a backup model
- Cache: SHA-256 keyed; repeated identical errors skip the model entirely
- Python 3.12 to 3.14: one event loop, never nested
Architecture
raw log -> slice errors -> normalize -> cache lookup -> AI diagnosis
-> confidence gate -> patch / validate / revert -> output
Key decisions
- Single event loop:
asyncio.new_event_loop()is opened once and never nested, so it works on Python 3.14. - Fast path: when source code is already available from disk (or the language is Node), the tool loop is skipped. One direct prompt returns structured JSON.
- Slow path: otherwise the model investigates with read-only tools, up to 8 steps.
- Failover: the primary model is tried first, then the backup.
- JSON repair:
llm/json_repair.py(ported from ollama-local-coding-agent) repairs malformed output before parsing. - No
shell=Trueanywhere: all subprocesses use list arguments.
Tech Stack
| Layer | Technology |
|---|---|
| Language | Python 3.12+ |
| LLM backend | OpenRouter API (free tier, no payment required); Ollama optional for local use |
| Terminal UI | Rich (written to stderr) |
| HTTP | Python stdlib urllib for OpenRouter calls |
| Async | asyncio and threading.Event |
| Testing | pytest, pytest-asyncio, pytest-mock |
| Config | python-dotenv |
The ollama Python package is installed as a dependency so the optional local backend works.
Quick Start
Requirements: Python 3.12+, git, and a free OpenRouter API key.
git clone https://github.com/rudra-agr/Exitzero.git
cd Exitzero
python -m venv .venv
source .venv/bin/activate # Windows Git Bash: source .venv/Scripts/activate
pip install -e ".[dev]"
cp .env.example .env
Then open .env and set your OPENROUTER_API_KEY. Never commit this file; it is gitignored.
Check that it works:
python -m exitzero --help
Usage
# Pipe directly
python script.py 2>&1 | python -m exitzero
# From a saved log file
python -m exitzero --file error.log
# Node / npm / Vite
npm run build 2>&1 | tee error.log && python -m exitzero --file error.log
# Pytest
pytest 2>&1 | tee error.log && python -m exitzero --file error.log
Flags
| Flag | Description |
|---|---|
--file PATH, -f PATH |
Read log from file instead of stdin |
--json |
Output the diagnosis as a JSON array on stdout (no UI) |
--dry-run |
Validate proposed fixes without executing them |
--no-apply |
Show the diagnosis only, no remediation |
--cache-stats |
Print cache statistics and exit |
--cache-clear |
Wipe cached diagnoses and exit |
Try the Demo Folder
exitzero_test/ contains small scripts with deliberate bugs:
python exitzero_test/src/type_bug.py 2>&1 | tee error.log
python -m exitzero --file error.log --no-apply
python exitzero_test/src/network_bug.py 2>&1 | python -m exitzero --no-apply
See exitzero_test/README.md for more.
Configuration
.env is searched upward from the current directory first, then in the project root. Variables already set in your shell always win.
| Variable | Default | Purpose |
|---|---|---|
EXITZERO_LLM_BACKEND |
openrouter |
openrouter or ollama |
OPENROUTER_API_KEY |
none | Required for OpenRouter; free at openrouter.ai |
EXITZERO_PRIMARY_MODEL |
meta-llama/llama-3.1-8b-instruct:free |
First model to try |
EXITZERO_BACKUP_MODEL |
google/gemma-2-9b-it:free |
Fallback model |
EXITZERO_LLM_TIMEOUT |
60 |
Seconds per model call |
EXITZERO_CACHE_PATH |
~/.exitzero_cache.json |
Cache file location |
EXITZERO_CACHE_TTL |
604800 |
Cache lifetime in seconds (7 days) |
EXITZERO_CACHE_MAX_ENTRIES |
500 |
Maximum cached diagnoses |
EXITZERO_CACHE_DISABLED |
0 |
Set to 1 to bypass the cache |
EXITZERO_HISTORY_PATH |
~/.exitzero_history.jsonl |
History file location |
EXITZERO_REMEDIATION_TIMEOUT |
60 |
Seconds for fix and validation commands |
Free OpenRouter models change often. If a default model stops working, set EXITZERO_PRIMARY_MODEL and EXITZERO_BACKUP_MODEL to any free model listed on openrouter.ai.
Local mode: set EXITZERO_LLM_BACKEND=ollama (or leave the API key empty) and run a local model such as qwen2.5-coder:7b.
Testing
python -m pytest -q -m "not live"
This runs 162 tests that need no network or API key. Three additional tests are marked live: they call a real model, so they need a working API key or a running Ollama. On a fresh checkout without either, run the command above.
AI Usage Disclosure
How AI was used to build this project
This project was built with AI assistance.
Claude (Anthropic) contributed in these ways:
- Code review and generation:Much of the code was generated with AI assistance, including core modules such as the log slicer, the normalizer, and the cache, CLI, and LLM pipeline.
- Code review against the spec: compared the codebase with the project specification and found mismatches, such as the
.envsearch order, the default models, and tool-call argument names. - Verification: helped plan and interpret a clean-clone check of the repository (install, test suite, CLI flags, cache, JSON output, and live diagnoses).
- Repository cleanup: helped remove a duplicated project folder, keep secrets out of the repo, and publish a clean history.
- Documentation: reviewed this README against the actual code and rewrote it.
AI output is consumed programmatically in exitzero/llm/pipeline.py.
The model receives the error log, the source code at the error location, and git diff context. It must return a structured JSON object (schema above). That JSON is:
- Parsed by
_parse()with malformed-JSON repair - Validated against the allowed categories and confidence levels
- Used to populate a
Diagnosisdataclass - Passed to
llm/confidence.pyto gate the action - Acted on by
llm/patcher.pyorremediator.py
The explanation text is shown to the user, but the structured fields decide what the system does.
Limitations
- Free OpenRouter models can rate-limit or fail on rapid consecutive calls. A failed call currently prints "Could not diagnose this error"; re-running usually works, and cached results are reused.
- Diagnosis accuracy depends on the model. Small free models can misidentify the root cause on complex errors.
- Automatic fixes require evidence from an observed file read. Some models call the investigation tools with argument names the tools do not accept, so the evidence step can fail and the diagnosis stays explain-only.
- Source code pull only works when the error contains a valid file path that exists on disk.
- npm and Vite errors produce long
node_modulesstack traces that can distract smaller models. - Automatic patch application requires git to be initialised in the project.
Future Improvements
- CI integration: a GitHub Actions step that runs ExitZero on failed workflows
- VS Code extension: inline diagnosis on failed test runs
- Fine-tuned model: train on real error and fix pairs for higher accuracy
- Multi-file patches: currently limited to single-file diffs
- Auto model selection: pick the best available free model by error type and language
- Web UI and Slack / Teams bot for teams that do not use the terminal
- Error pattern library: a local database of seen errors and known fixes for instant offline diagnosis
Acknowledgements
exitzero/llm/json_repair.py is ported from the jsonRepair.ts helper in the ollama-local-coding-agent project.
Log in or sign up for Devpost to join the conversation.