Agent Error Log
Give your AI coding agent a memory for its own mistakes — and make "log before fixing" mechanically enforced instead of merely encouraged.
A tiny, dependency-free system for anyone who works with AI coding assistants or builds their own agent loops. Three text files + two small tools + one git hook + optional harness blockers = an agent that:
- Logs every error with its root cause — before it fixes it (if you can't explain why it broke, you haven't understood it).
- Refuses to commit a fix unless the error was logged — enforced by a git hook, not by good intentions.
- Boots every session calibrated — health-checks the log, re-reads the open errors, and re-surfaces the distilled lessons from past failures.
No specific model, provider, or framework required. If your agent can read a
.txt file and run a terminal command, it can use this.
Who is this for?
- People using AI coding assistants — Claude Code, Codex, Cursor, Gemini CLI, Copilot, or any chat-to-code tool that can execute commands.
- People building custom agent loops — local models (Ollama, llama.cpp),
hosted APIs (OpenAI, Anthropic, any OpenAI-compatible endpoint), LangChain
or hand-rolled
whileloops. - Anyone tired of the same bug being introduced twice.
The workflow lives in plain text files and shell — it is model-agnostic.
The files that talk to your agent (rules.txt, AGENTS.md) use simple
imperative language any LLM follows.
Why this exists
AI agents are stateless between sessions. They re-learn your project's failure modes from scratch every single time — you fix a bug, and a week later the agent reintroduces the exact same one because nothing remembered the cause.
This system fixes that with three ideas:
| Idea | Implementation |
|---|---|
| Structure | Every error entry records symptom → cause → fix → status in a machine-validated template |
| Causes before fixes | The CAUSE field is written before the fix starts, and the linter refuses entries without it |
| Enforcement | A git hook blocks code commits that don't reference a logged error — the rule is mechanical, not motivational |
What's inside
agent-error-log/
├── README.md ← this file
├── AGENTS.md ← instructions your AI agent should read
├── CHANGELOG.md ← release history
├── CONTRIBUTING.md ← how to contribute
├── LICENSE ← MIT
├── .gitignore
├── start.py session bootstrap (STEP 0 health check)
├── check_errors.py error-log tooling: validate / gate / add / archive
├── _test_errors.py 139 unit tests for the tooling
├── git-commitmsg-hook.sh the log-before-fix git gate
├── hooks/ optional harness-level --no-verify blockers
├── rules.txt RULES template (how the agent behaves)
├── errors.txt ERROR LOG template (works out of the box)
├── notes.txt NOTES template (session notes)
└── start.bat Windows launcher for start.py
stdlib-only Python 3 and plain shell — no pip installs, no build step. Works on Windows / macOS / Linux.
Quick start (3 steps)
- Copy the folder to the root of your project (see Git gate placement below for other locations). Rename it if you like.
- Make it yours — edit the
<YOUR PROJECT NAME>/<YOUR ASSISTANT NAME>placeholders inrules.txtandnotes.txt, fill in section 4 (your project map), and replace the example entries inerrors.txtwith your real ones (keep the section-5 template intact — it's the entry format). If you renameerrors.txt, updateLOGat the top ofcheck_errors.pyand the hook'sLOGNAMEenv var. - Run it:
python start.py # boots the session: health check + open errors + notes python _test_errors.py # sanity-check the tooling (all 139 should pass)
Adopting with a custom agent (no AGENTS.md support)
If your agent doesn't honor the AGENTS.md convention, just paste its
contents into your agent's system prompt / instructions. That's the whole
integration — the rest of the system is files and commands your agent
already knows how to use.
See it in action
$ python start.py
================================================================================
AGENT SESSION BOOTSTRAP
when : 2026-08-08 23:44
workspace : /path/to/agent-error-log
================================================================================
--------------------------------------------------------------------------------
STEP 0 - ERROR-LOG HEALTH CHECK (check_errors.py):
3 entrie(s): 0 error(s), 0 warning(s).
RESULT: log healthy - safe to code.
ACTIVE / UNRESOLVED ERRORS (non-FIXED, from the error log):
[2026-08-07] AREA: image resize service timeouts
STATUS: OPEN.
[2026-08-08] AREA: search API rate limit
STATUS: MITIGATED.
The git gate blocking a fix that was never logged:
$ git commit -m "fix sprite tracking (AREA: player sprite color WRONG)"
GATE FAILED — no entry for 'player sprite color WRONG'. LOG BEFORE FIXING:
add an entry first (python check_errors.py --add), then fix.
commit-msg BLOCKED: the error "player sprite color WRONG" is NOT logged.
And passing, once the error is logged:
$ git commit -m "fix sprite tracking (AREA: player sprite color WRONG)"
found: [2026-08-08] AREA: player sprite color WRONG (line 30)
GATE PASSED — the error is logged. You may now apply the fix.
commit-msg OK: "player sprite color WRONG" is logged — fix may land.
The workflow it enforces
session start → python start.py (health check + context, STEP 0)
error happens → check_errors.py --add (log FIRST — CAUSE before the fix)
about to fix → check_errors.py --has-entry "<AREA>" (gate: exit 0 only
if the error is logged)
fix lands → git commit -m "... (AREA: <what broke>)"
log grows → check_errors.py --archive-days 30 --apply
(old FIXED entries move to an ARCHIVED section)
drift appears → the linter flags it at the next session start, automatically
Install the git gate (optional but recommended)
git init # if your project isn't a repo yet
cp git-commitmsg-hook.sh .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
Or do it all in one command — templates, hook, health check, self-test:
python check_errors.py --init
Git gate placement — the hook assumes check_errors.py sits at the repo
root. If you placed the folder elsewhere, tell the hook where:
# e.g. folder at tools/agent-error-log/
export AGENT_ERROR_LOG_DIR="$PWD/tools/agent-error-log" # before committing
Docs-only commits and log-only commits pass automatically (the log itself must still validate).
Known limitation: --no-verify
Git's --no-verify flag skips all hooks — including this one. Any agent
or human that commits with git commit --no-verify bypasses the gate
entirely.
This is accepted by design: git hooks are advisory, not a security
boundary. What we can do is make the bypass deliberate instead of silent, at
the layers where the agent actually lives. The hooks/ folder ships
ready-to-use blockers for the common cases.
Practical layers, in order of value:
- Instruct your agent — the most effective layer.
AGENTS.md(andrules.txt§2) say: never usegit commit --no-verify; if the hook blocks you, log the error first and commit again. LLM agents follow explicit instructions reliably — this closes the loop for the common case. - CI as a backstop (shipped) — the
commit-gatejob re-runs the gate on every pushed commit:python check_errors.py --check-commitfails the build if the commit message names no logged error, and thetests + lintermatrix catches a broken log. Both can be required before merge. - Harness-level blocking (shipped in
hooks/) — block the flag where the agent runs:hooks/block-no-verify.sh— a git alias wrapper for your own shell (rejectsgit commit --no-verify/-n).hooks/block-no-verify-hook.sh+claude-code-settings.json— a Claude CodePreToolUsehook that blocks the command (exit 2, reason shown to the model).hooks/block-no-verify-hook.sh+vscode-agent-hooks.json— a VS Code agent hook with the same guard (self-filters ontool_name, since VS Code does not apply matchers). Install steps for all three:hooks/README.md.
- Server-side hooks — pre-receive hooks on self-hosted git (Gitea/GitLab) run on the server and cannot be skipped by the client. Overkill for a solo project, but the only truly unbypassable option.
Making the gate required (branch protection)
The CI checks report failures but don't block pushes by default (a red check
on master is advisory for the owner). To make the gate a hard requirement:
- GitHub → Settings → Branches → Add branch protection rule.
- Branch name pattern:
master(ormain). - Tick Require status checks to pass before merging.
- Tick the checks:
commit-message gate (log-before-fix)andCI. - (Optional) tick Do not allow bypassing the above settings for admins.
With that, a --no-verify commit cannot land on master — the message is
re-checked on the server, where the flag does not exist.
Shipping a change (PR workflow)
With branch protection live, direct pushes to master are rejected —
GH006: Protected branch update failed … 7 of 7 required status checks are expected — because a fresh commit has no CI checks yet. Every change lands
via pull request:
- Branch off
masterand commit with the(AREA: <logged error>)marker in the message (matching an entry inerrors.txt):git commit -m "fix: … (AREA: search API rate limit)". - Push the branch, open a PR against
master. The sixtests + lintermatrix jobs run on the PR head. - Squash-merge once checks are green, keeping the
(AREA: …)marker in the squash title. The merge push re-runs CI and the commit-message gate onmaster— a missing marker leaves the gate red.
The gate job skips PR events on purpose: PRs are gated when the merge
lands, so the squash title is exactly what gets re-checked on master.
Tooling reference
| Command | What it does |
|---|---|
python check_errors.py |
validates every entry (template fields + canonical statuses FIXED | PARTIAL | OPEN | MITIGATED | WORKAROUND), flags duplicates and bad dates. Exit 0 = healthy |
--has-entry "<AREA>" |
mechanical gate: exit 0 only if the error is already logged |
--add |
interactive scaffolder — writes a template-perfect entry above section 5 |
--archive-days N |
preview FIXED entries older than N days |
--archive-days N --apply |
actually move them into the ARCHIVED section (idempotent) |
--log PATH |
point the tooling at any error log |
--lessons |
distill recurring cause keywords from the error log into lessons (preview) |
--lessons --apply |
write the distilled LESSONS section into rules.txt |
--check-commit FILE |
gate on a commit-message file: exit 0 only if it names a logged error (AREA:/LOG: marker) — the CI server-side backstop |
--init |
one-command adoption: scaffold errors.txt/rules.txt/notes.txt, install the commit-msg hook, health-check, run the tests (--target DIR, --no-tests) |
Customization
- Filenames — rename
rules.txt/errors.txt/notes.txtand update the constants at the top ofstart.py,LOGincheck_errors.py, and the hook'sLOGNAMEenv var (defaulterrors.txt). - Statuses — edit
STATUSESincheck_errors.py(and the docs inerrors.txt) to match your vocabulary. - Lessons —
rules.txt§7 ships five generic root-cause lessons (data robustness, model quirks, environment, screen-vision, log discipline). Replace them with your own as your log grows — that section is the permanent memory. Regenerate it automatically from your error log:python check_errors.py --lessons --apply. - Lesson clusters — lessons group by shared keywords, so two entries that
merely share a word can chain into one cluster. Good enough to group
related failures, not a perfect taxonomy — inspect before
--apply. - Python interpreter — the hook uses
pythonby default; override with thePYTHONenv var. - Hook placement — the hook finds
check_errors.pyat the repo root by default; override withAGENT_ERROR_LOG_DIR(see Git gate placement). - Log path —
LOGNAMEdefaults toerrors.txtat the repo root. If the log lives in a subfolder, set it to the repo-root-relative path (e.g.LOGNAME=docs/errors.txt) — the hook matches staged paths verbatim.
Compatibility & security
- Python 3.8+, stdlib only. The shell hook runs under git-bash / sh (Windows, macOS, Linux).
- The error log may contain sensitive details (paths, payloads, stack
traces). Never log credentials or secrets — keep the repo private if
in doubt.
.gitignorealready excludes__pycache__/and*.pyc. - The hook invokes Python from
PATH; override withPYTHONif your interpreter is elsewhere.
FAQ
- Do I need a specific LLM? No. Any model that can read text and run commands works.
- Do I need pip / npm? No. Zero dependencies.
- Does it work on Windows? Yes — UTF-8 handling is built in, plus a
start.batlauncher. - Can I log unicode (café, em-dash) on Windows? Yes — both
stdoutandstdinare reconfigured to UTF-8, so piped unicode text is stored as-is, never double-encoded. - I already keep a NOTES.md — why this? NOTES.md is unstructured and unenforced. This adds a machine-validated format, a hard commit gate, and automated session checks on top of the same idea.
- Can I use my own file names? Yes — see Customization.
- I copied the tool to a scratch folder — will it touch my real repo? No.
Default paths resolve relative to the script location (
HERE), so a scratch copy logs next to itself. Point at your real log from anywhere with--log path/to/errors.txt.
Development
python _test_errors.py # 139 tests: parsing, validation, gate, add, archive, lessons, init
The tests build throwaway logs in temp dirs — they never touch your real
errors.txt.
Parsing walks each entry forward to the next header, so worst case is O(n²) on pathological files; for real logs (tens to hundreds of entries) it is instant, and the validator is fine at thousands of lines.
CI — a GitHub Actions workflow (.github/workflows/ci.yml) runs the unit
tests, the linter, and a syntax check of every hook script (git-commitmsg-hook.sh
and hooks/*.sh) on every push and pull request, across Python 3.9–3.12 on
Linux and Windows. This is the enforcement backstop described in Known
limitation: --no-verify: even a bypassed hook can't hide a broken log or a
failing test.
Releases — the version at the top of CHANGELOG.md is the single source
of truth. Bump it and push to master: the release workflow
(.github/workflows/release.yml) creates a vX.Y.Z tag and opens a draft
GitHub Release with that changelog section as the body — publish it on the
Releases page when ready.
Companion tool
- agent-decision-log - logs what your agent chose and why, so the next session starts from "we already decided X" instead of re-exploring it. Proactive memory.
Two tools, same shape, same lifecycle verbs: this one prevents repeating failures, the companion prevents repeating exploration.
License
MIT — see LICENSE.
Installing with pip (optional)
The single-file adoption story is unchanged - copy check_errors.py into your
project and you are done. The tool is also pip-installable with zero runtime
dependencies:
pip install agent-error-log
error-log --help
- The package version is derived from the git tag (setuptools-scm), which the release workflow creates from CHANGELOG.md - there is no version to drift.
- Run from the installed package, default paths (
errors.txt,rules.txt) resolve against your current directory; an in-place copy keeps resolving against the file's folder. --initworks identically from an installed copy (built-in templates).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_error_log-0.9.0.tar.gz.
File metadata
- Download URL: agent_error_log-0.9.0.tar.gz
- Upload date:
- Size: 67.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eec8e41b432b856bd239fce57eaca928ddecd3d422b137bd5e80ca20dfb25e43
|
|
| MD5 |
d8dcc9b72e3bbf17654917f7f411f9ae
|
|
| BLAKE2b-256 |
561d826c0c3e64f3f583a0ba169361e1f4a047e006003001fbb2b2436da2c3cf
|
Provenance
The following attestation bundles were made for agent_error_log-0.9.0.tar.gz:
Publisher:
publish.yml on vartiainen1/agent-error-log
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_error_log-0.9.0.tar.gz -
Subject digest:
eec8e41b432b856bd239fce57eaca928ddecd3d422b137bd5e80ca20dfb25e43 - Sigstore transparency entry: 2414676394
- Sigstore integration time:
-
Permalink:
vartiainen1/agent-error-log@303c71b2e45260b704e9bd927b26ee22ba0b784f -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/vartiainen1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@303c71b2e45260b704e9bd927b26ee22ba0b784f -
Trigger Event:
release
-
Statement type:
File details
Details for the file agent_error_log-0.9.0-py3-none-any.whl.
File metadata
- Download URL: agent_error_log-0.9.0-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f7761f469ef19625622ecfe2d1eee65f45877b48960f8d872b1612568c2f44a
|
|
| MD5 |
1c7be973d67ba5e94df5a99230feeb1b
|
|
| BLAKE2b-256 |
973c789dba39db6c5a1c1c2dac64b6eb02095b237bf2bf5af914d94ff2788486
|
Provenance
The following attestation bundles were made for agent_error_log-0.9.0-py3-none-any.whl:
Publisher:
publish.yml on vartiainen1/agent-error-log
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_error_log-0.9.0-py3-none-any.whl -
Subject digest:
2f7761f469ef19625622ecfe2d1eee65f45877b48960f8d872b1612568c2f44a - Sigstore transparency entry: 2414676423
- Sigstore integration time:
-
Permalink:
vartiainen1/agent-error-log@303c71b2e45260b704e9bd927b26ee22ba0b784f -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/vartiainen1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@303c71b2e45260b704e9bd927b26ee22ba0b784f -
Trigger Event:
release
-
Statement type: