Article · Feb 27, 2026

ISSUES.md as a learning log: the flat-file bug tracker that compounds

ISSUES.md is a flat markdown bug tracker that doubles as a regression detector and training doc for AI-built codebases. How the pattern works.

If you have inherited a Claude Code codebase, 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. This is a companion to the four-part system master post.

Why a markdown file beats Linear or GitHub Issues for Claude Code work

Markdown lives in the repo. A new contributor cloning the repo gets the bug history alongside the source. A Claude Code 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.

Claude Code can grep markdown directly. Claude Code, Aider, Cursor’s agent mode: any 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 integration friction tends to make AI sessions skip them.

Friction kills the capture rate. 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 gap between the markdown approach and a hosted tracker is not a multiplier on capture rate. It is the difference between “logged” and “forgotten.”

For solo and small-team Claude Code work, those 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. “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. Not “what the fix is” (that is in the diff). 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 turns the file from a list into a graph.

The Related line is the multiplier on the whole pattern. Without it, ISSUES.md is a chronological list of fixed bugs. With it, it 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 before they shipped. 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 it walked straight to the old reasoning.

The payoff showed up a few specific ways. Entry #047 was 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 it, the same call-site workaround as #009 would have been applied and #047 would have been re-opened when a third provider wrapped its payload.

Entries #023, #034, and #051 all linked to #015 (a state-machine transition bug in a workflow). When the count hit four, the clustering made it obvious: the original fix was a workaround and a real refactor of that state machine was the actual next move.

Onboarding also got cheaper. “Read CLAUDE.md, then read the last 20 ISSUES.md entries” became the canonical first hour for any new contributor or new AI session. The Related links make the file scan-friendly: you read recent entries, follow the links to older ones that are still load-bearing, and skip the ones that have been superseded.

What to log vs what to skip

The discipline that keeps the file useful: 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 likely to recur.
  • Bugs whose fix involves a non-obvious trade-off.
  • Bugs the model got wrong on the first attempt.

Skip:

  • Typos and pure copy-edits.
  • Dependency upgrades with no semantic effect.
  • Bugs caught in dev that never reached staging.

The threshold: would a future contributor benefit from reading this entry six months from now? If yes, log it.

Workflow integration: feeding ISSUES.md back into Claude Code

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.

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 from the session. It produces a first draft of all four parts in under a minute. You edit for accuracy and commit. Capture rate goes from “I’ll write it later” to “before I close this chat.”

On the project where ISSUES.md hit 78 entries, Pattern 2 was responsible for roughly two-thirds of them. The friction reduction is the difference between the discipline working and the discipline drifting.

Failure modes to watch for

Drift through over-formality. When ISSUES.md grows a template with required fields, severity ratings, and label taxonomies, the friction returns and the file goes stale. The four-part shape is the maximum formal structure that still keeps the capture rate high. Resist the urge to make it more like Linear.

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 making ISSUES.md reading part of the debugging workflow (see the CLAUDE.md integration above) and referencing entries explicitly in code review. Referenced entries get read. Unread entries do not.

Abandonment after the first ten entries. Solo project, no external accountability. The discipline drifts after the easy ones are written. The AI-session integration is the structural fix: if drafting the entry is the last action of every debugging chat, abandonment is hard.

If you are taking over a Lovable, Cursor, or Claude Code project and want this kind of documentation discipline installed on day one, let’s talk.

Frequently asked questions

Why use a flat markdown file instead of a real bug tracker like Linear or GitHub Issues?

Markdown lives in the repo and travels with the code. A new contributor gets the bug history alongside the source instead of clicking through a separate tool. Claude Code can grep ISSUES.md as a primary context source; the same is not true of Linear or GitHub Issues without an integration. The friction of opening a tracker also kills the capture rate: opening a markdown file in the editor you are already in keeps bugs from going unlogged. For solo and small-team work, ISSUES.md beats a heavyweight tracker on the metric that matters most: whether the bug actually gets recorded.

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 boundary. (4) Related: links to other entry numbers that share similar shape, fixed an underlying cause this entry depends on, or might regress this one. The Related line 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 the file holds 78 closed entries at 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 (several thousand entries before this becomes a real constraint). When the file does become unwieldy, split by year or 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?

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 entries as background context for any debugging conversation, often surfacing related entries before you have mentioned them. When you log a new bug interactively, you can ask Claude to grep ISSUES.md for similar-shape entries and propose Related links. That 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 Claude Code engineering teams (1-5 people), ISSUES.md is often enough on its own. For larger teams, it complements Linear or Jira by capturing the technical reasoning 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 rule is self-reinforcing once you hit a few cases where an old entry caught a regression you would otherwise have shipped. 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.