Article · Feb 27, 2026
ISSUES.md as a learning log: a 78-issue tracker pattern for AI-built projects
ISSUES.md is a flat markdown bug tracker that doubles as a regression detector and training doc. The pattern that works after 78 closed entries.
If you have inherited an AI-built codebase, you have probably noticed that the commits do not explain the why, the PRs do not explain the why, and the original author was a stateless model in a chat window six weeks ago. The reasoning that produced the code is gone the moment the model finishes writing the file. ISSUES.md is the structural answer to that problem. A flat markdown file at the root of the repo. One entry per bug. Four parts each. After about 78 closed entries on a recent client project of mine, the file became three things at once: a regression detector, a training doc, and a counterweight to the missing commit reasoning. This post is a deep-dive on the pattern, written as a companion to the four-part system master post.
Why a markdown file beats Linear or GitHub Issues for AI-built work
Three reasons.
First, markdown lives in the repo. It travels with the code. A new contributor cloning the repo gets the bug history alongside the source. A future AI session reading the codebase reads ISSUES.md as part of the same context window. A Linear board requires a separate login, a separate URL, a separate API to query; in practice that means most readers never look at it.
Second, AI coding tools can grep markdown. Claude Code, Aider, Cursor’s agent mode, and any other tool that reads files from the repo can read ISSUES.md as a primary context source. The model uses the entries as background context for any debugging conversation. Linear and Jira require integrations or MCP servers to expose this same data, and even then the latency and the integration friction tend to make AI sessions skip them.
Third, friction kills the capture rate. The hardest thing about a bug tracker is getting bugs into it. A new tab, a login screen, a “Create new issue” form with required fields you do not yet know how to fill, an estimate field, a priority dropdown. Markdown is Cmd+P → ISSUES.md → type → save. The capture rate on the markdown approach is not 5x higher than on Linear; it is roughly the difference between “logged” and “forgotten.”
For solo and small-team AI-built work, those three reasons add up to ISSUES.md being the better choice. For cross-functional teams with PMs, designers, and QA, ISSUES.md should complement a real tracker rather than replace it. The tracker handles assignment, scheduling, and stakeholder visibility; ISSUES.md handles the engineering reasoning that the tracker is bad at preserving.
The four-part entry structure
Every entry has four parts. The format is not the point; what each part forces you to write is the point.
## #017 · Webhook payload nested one level deeper
**Repro**
Trigger the form submission. Inspect the workflow run. Note that
every field arrives as `undefined`. The console shows the run as
"completed successfully" with no errors.
**Root cause**
The provider wrapped the payload between Tuesday and Wednesday.
Yesterday's payloads have shape `data`. Today's have shape
`{ data: { ... } }`. The downstream code reads `data.field_name`,
which is now `undefined.field_name`, which is `undefined`.
**Fix reasoning**
Read from `payload.data.*` to handle both shapes. Add a
shape-validation check at ingest so the next provider change
crashes loudly instead of silently. The check is at the boundary,
not at every read site, because the rest of the codebase should
not have to know about the provider's payload structure.
**Related**
- Surfaces #009 (similar shape mismatch on the Stripe webhook
six weeks ago, fixed at the call site instead of the boundary).
- Will likely surface again at #028 (similar Twilio webhook
payload, not yet integrated).
Each part has a job:
- Repro captures the bug in a form a future reader (human or AI) can rerun. The payoff is that “I cannot reproduce” becomes a non-excuse.
- Root cause forces you to distinguish symptom from cause. “Form fields are undefined” is a symptom; “the provider wrapped the payload” is the cause. Bugs logged at symptom level produce duplicate entries when the same root cause surfaces somewhere else.
- Fix reasoning is the part that earns ISSUES.md its keep three months from now. It is not “what the fix is” (that is in the diff). It is why this fix is correct and what alternatives were rejected. When an analogous bug shows up at a different boundary, the reasoning ports.
- Related links to other entries that share shape, depend on the same fix, or might regress this one. This is the part that turns the file from a list into a graph.
The Related-line graph: how 78 entries become a regression detector
The Related line is the multiplier on this whole pattern. Without it, ISSUES.md is a chronological list of fixed bugs. With it, ISSUES.md is a directed graph where edges encode “this bug is connected to that bug.”
After 78 entries on the project I mentioned, the graph caught regressions automatically. A new bug filed against the webhook ingest code would be drafted with a “Related: #017” line on first pass, and the next person reading the bug would walk straight to the old entry and the old reasoning. Three concrete ways this paid off:
- Catching incomplete fixes. Entry #047 was filed against a similar payload-wrapping issue on a different provider. The Related-link to #017 surfaced the original fix reasoning (“validate at the boundary, not the call site”). Without the link, the same call-site fix as #009 would have been applied, and #047 would have been re-opened a month later when a third provider wrapped its payload.
- Building cross-cutting refactors. Entries #023, #034, and #051 all linked to #015 (a bug about state-machine transitions in a workflow). When the count hit three, the pattern was clear: the original fix was a workaround, not a fix, and a real refactor of that state machine was the next move. The Related-line clustering surfaced that.
- Onboarding. When a new contributor (or a new AI session) joins the project, “read CLAUDE.md, then read the last 20 ISSUES.md entries” is the canonical first hour. The graph makes the file scan-friendly; you read the recent entries, follow the Related links to older entries that are still load-bearing, and skip the ones that have been superseded.
The cost of the Related line is one minute per entry. The benefit is the file becoming a regression detector, not just a log.
What to write down vs what to skip
The discipline that keeps the file useful is log every bug that produced root-cause work, skip the ones that did not.
Log:
- Bugs whose root cause was non-obvious.
- Bugs that took more than 30 minutes to diagnose.
- Bugs that surface a class of issues that might recur.
- Bugs whose fix involves a non-obvious trade-off.
- Bugs the AI tool got wrong on the first attempt.
Skip:
- Typos and one-line copy-edits.
- Pure dependency upgrades with no semantic effect.
- Bugs whose entire reasoning is “I read the docs and the docs were right.”
- Bugs caught in dev that never reached even staging.
The threshold is roughly “would a future contributor benefit from reading this entry six months from now?” If yes, log it. If the entry would only ever be the equivalent of “fixed typo in line 42,” skip it.
Workflow integration: Claude Code reads ISSUES.md
The biggest force-multiplier on ISSUES.md is feeding it back into the AI session. Two integration patterns:
Pattern 1: Reference ISSUES.md in CLAUDE.md.
## Debugging context
Before suggesting a fix for any bug report, grep ISSUES.md for
related entries. If a Related-link is plausible, surface it in
your response and propose the link before drafting the fix.
This puts the discipline in the model’s standing instructions. Every debugging session starts with a grep of the past, which is exactly the human discipline you want enforced.
Pattern 2: Use ISSUES.md as the closing artifact.
When you finish debugging a bug with Claude Code’s help, the last action of the conversation is “draft an ISSUES.md entry for this.” The model has full context (you just walked through the diagnosis with it). Drafting the entry interactively takes one minute. The model produces a first draft of all four parts; you edit for accuracy and commit. The capture rate goes from “I’ll write it later” (which means never) to “before I close this chat” (which means always).
On the project where ISSUES.md hit 78 entries, this second pattern was responsible for roughly two-thirds of the entries. The friction reduction is the difference between the discipline working and the discipline drifting.
Pitfalls
Three failure modes I have seen on real projects.
- Drift through over-formality. When ISSUES.md grows a template with required fields, severity ratings, label taxonomies, and so on, the friction returns and the file goes stale. Resist the urge to “make it more like Linear.” The four-part shape is the maximum formal structure that still keeps the capture rate high.
- Drift through under-use. If nobody reads the file, the Related lines do not earn their keep, the graph does not form, and entries accumulate as a flat list. The cure is to make reading ISSUES.md part of the debugging workflow (see the CLAUDE.md integration above) and to reference entries explicitly in code review.
- Abandonment after the first ten entries. Solo project, no external pressure, the discipline drifts after the easy entries are written. The cure is the AI-session integration; if drafting the entry is the last action of every debugging chat, abandonment is structurally hard.
The pattern is robust to most failure modes that hit issue trackers, because the core file is so simple that there is not much to abandon. As long as someone is debugging bugs and someone is logging them, the file works.
The compound payoff
The cost of an ISSUES.md entry is one minute. The benefit is one bug’s reasoning preserved, in a form that survives turnover, AI session boundaries, and the AI’s missing commit reasoning. After 78 entries, the file becomes a project asset that compounds in value: every new entry can link backward, every new contributor reads the accumulated history, every new AI session has a richer context.
If you are inheriting a Lovable, Cursor, or Claude Code project and the codebase looks fine but feels haunted, the missing piece is usually this. Not tests. Not types. The connective tissue between the work that has happened and the work that is about to happen.
If you are running AI-assisted work across North America, the UK and Ireland, the EU and EEA, or the ANZ region, and you want this discipline installed on day one of your next engagement, let’s talk.
Related reading
- How I document AI-built projects: a CLAUDE.md, ISSUES.md, and prompts/ workflow : the master post this is a deep-dive of.
- How to audit a Lovable app after the BOLA disclosure: a 6-hour rotation playbook : what the discipline looks like under disclosure-day pressure.
Frequently asked questions
Why use a flat markdown file instead of a real bug tracker like Linear or GitHub Issues?
Three reasons. First, markdown lives in the repo and travels with the code; future contributors read the bug history alongside the source instead of clicking through a separate tool. Second, AI coding tools like Claude Code can grep ISSUES.md as a primary context source; the same is not true of Linear or GitHub Issues without an integration. Third, the friction of opening a tracker often loses the bug; opening a markdown file in the editor you are already in keeps the capture rate high. For solo and small-team AI-built work, ISSUES.md beats a heavyweight tracker on the metric that matters most: whether the bug actually gets logged.
What goes in an ISSUES.md entry?
Four parts per entry. (1) Repro: the steps that produce the bug, written so a human or AI can reproduce it cold. (2) Root cause: what is actually broken, in one paragraph. Not the symptom, the cause. (3) Fix reasoning: what changed and why this fix is correct (not just what the fix is). The reasoning line is the one that pays off later when an analogous bug shows up at a different location. (4) Related: links to other entry numbers that surface similar shape bugs, fixed an underlying cause this entry depends on, or might regress this one. The Related line is what turns the file from a list into a graph.
How big can ISSUES.md get before it stops being useful?
On a recent client project of mine the file holds 78 closed entries and is roughly 1,200 lines, and it is still the most-read file in the repo besides CLAUDE.md. The natural ceiling is wherever grep stops feeling fast (in practice, several thousand entries before this becomes a real constraint). When the file does become unwieldy, the right move is not to delete old entries but to split by year or by subsystem (ISSUES-2024.md, ISSUES-payments.md). The Related lines that cross files still work because grep walks multiple files in one pass.
How does Claude Code use ISSUES.md?
Two ways. First, when you start a new chat session, Claude Code reads CLAUDE.md automatically and ISSUES.md if you mention it (some teams symlink ISSUES.md into the CLAUDE.md context to make the read automatic). The model uses the entries as background context for any debugging conversation, often surfacing related entries before you have mentioned them. Second, when you log a new bug interactively, you can ask Claude to grep ISSUES.md for similar shape entries and propose Related links; this is the workflow that turns the file into a graph instead of just a list.
Does ISSUES.md replace a Linear or Jira board?
Not for cross-functional teams. If you have a product manager filing tickets, a designer tracking work, and a QA team running cycles, you need a tool that supports their workflows. ISSUES.md is a developer's debugging log, not a project management tool. For solo practitioners and small AI-built engineering teams (1-5 people), ISSUES.md is often enough on its own. For larger teams, ISSUES.md complements Linear or Jira by capturing the technical reasoning that those tools are bad at storing; the ticket says "users cannot log in," the ISSUES.md entry says why and how the fix works.
How do I avoid ISSUES.md going stale?
Make logging the entry part of closing the bug, not a separate step. The entry is the definition of done. If you fix a bug without logging it, the bug is not done. This is a cultural rule that has to be enforced; it is also self-reinforcing once you have hit a few cases where an old entry caught a regression you would otherwise have shipped. On a personal project, the discipline tends to drift after the first ten entries; on client work where the file is referenced in code review, drift is rare. If your file is going stale, the underlying problem is usually that nobody is reading it, which means the Related lines are not earning their keep yet.