2026

July 12, 2026

Your Codebase Should Learn From Every Debugging Session

A difficult trace is evidence about how a codebase communicates. The breadcrumbs skill turns that evidence into small repairs: clearer names, explicit contracts, useful errors, honest comments, and documentation in the place readers already look. The result is a codebase that gets easier to understand each time an agent or developer works through it.

S
Sascha Becker
Author

10 min read

Your Codebase Should Learn From Every Debugging Session

An agent needed to change the retry delay for checkout. It searched the checkout service, the network client, the environment schema, and the retry helper. The value was eventually found under analytics/config.ts, where it had landed during a refactor two years earlier.

The agent changed five seconds to eight, ran the tests, and finished the task.

The next agent will perform the same search.

Nothing was wrong with the investigation. It found the right answer. The waste was leaving the codebase in a state that would produce the same wrong turns again. A human developer joining the team next month would pay the same tax.

My first version of the breadcrumbs skill tried to solve this by recording the finding. It asked the agent to write an anchored note into AGENTS.md or a dedicated breadcrumb file: the retry delay lives over there, this workaround exists because of Safari, this function must run inside a transaction.

That sounds responsible. It also creates another system to maintain, another place that can drift, and another habit every future reader must know about before it helps them. I had designed a better investigation diary. I had not made the codebase clearer.

The reworked skill takes the opposite approach:

If a trace exposes avoidable confusion, repair the thing that caused it while the evidence is fresh. Move or rename the retry setting. Make the transaction part of the function contract. Improve the error that hid the failing field. Explain the Safari constraint beside the workaround. Correct the stale setup guide.

The next reader should benefit without knowing that the skill exists.

A trace is telemetry

Programmers do not read a repository from top to bottom. They follow cues. A filename suggests ownership. A symbol name suggests behavior. An import opens a path into another module. An error message points toward a cause, or away from it.

Research on programmer navigation calls this information scent. In a study of professional programmers debugging a real application, a model based on scent and navigation topology predicted where programmers went better than comparable models that ignored scent.1 The words and paths available in the environment shape the investigation.

That gives us something more useful than a vague feeling that code was hard to understand. The shape of the trace is evidence.

  • You followed a plausible name into the wrong subsystem. The information scent is misleading.
  • You kept returning to the same hub and jumping manually between layers. The navigation topology has no visible edge or canonical entry point.
  • You inspected every caller to discover one precondition. The contract is hidden.
  • You compared several values because each looked authoritative. Ownership is fragmented.
  • You reached the cause only after an unhelpful downstream failure. The diagnostics are opaque.

Do not save the search transcript. Use it to identify which part of the codebase taught the wrong lesson.

Repair the source of the confusion

Once you name the smell, the repair often becomes obvious. The important choice is its form. A note is easy to write, but it is usually the weakest durable form.

The breadcrumbs skill uses a repair ladder. Start at the top and move down only when the stronger form does not fit safely:

  1. Delete the obsolete or misleading thing.
  2. Make the truth visible through naming, placement, structure, or an explicit dependency.
  3. Encode the contract in types, schemas, validation, assertions, or tests.
  4. Improve the error or diagnostic context.
  5. Explain a reason that the code cannot express.
  6. Update an existing documentation surface readers already use.

This is not a ranking of importance. It is a ranking of how directly the repair reaches the next reader. Code that cannot be misused is stronger than a paragraph asking people not to misuse it.

Hidden contracts should become executable

Suppose you discover that saveOrder must be called inside an existing database transaction. The weak repair is a note:

ts
// Must be called inside a transaction.
await saveOrder(order);

It helps the reader who notices it and remembers to comply. It does nothing for the caller who misses it. A stronger API makes the obligation visible:

ts
await saveOrder(transaction, order);

Depending on the language and architecture, the best form might be a transaction-scoped repository, a state-specific type, an assertion at the boundary, or validation with a focused contract test. Bertrand Meyer's Design by Contract gives the useful vocabulary: preconditions describe the caller's obligations, postconditions describe the operation's guarantees, and invariants describe what remains true across operations.2 You do not need Eiffel syntax to make those facts explicit.

Errors should remain useful at a distance

invalid configuration is technically correct and operationally useless. If the trace reveals that region became empty after environment interpolation, the failure should say so at the earliest boundary that has enough context:

text
checkout config: region is required after environment interpolation

Good error types preserve the operation, the relevant target, and the original cause. The Go project's PathError is a compact example: it carries the operation, path, and underlying error, so it remains useful even when printed far from the failed call.3 Structured details also let tools react without parsing prose. The repair still needs judgment. Do not put tokens, personal data, or an entire request body into a log just to make it informative.

Comments spend attention

Comments are not free clarification. They are part of the reader interface and compete with the code for attention.

A 2025 eye-tracking study found that comments redirected about 23 percent of visual attention away from code, while their effect on comprehension varied by snippet. Some helped. Some made performance worse. Quality and context decided which.4 Google gives the practical rule: if a reviewer needs an explanation, clearer code is usually the first response. A comment is appropriate when it carries information the code cannot, especially the reason behind a surprising choice. An explanation left only in the review tool helps nobody reading the code later.5

This comment earns its keep:

ts
// Reading layout here forces Safari to apply the pending style before focus.
// Removing it restores the selection jump covered by safari-focus.spec.ts.
void input.offsetHeight;
input.focus();

// Needed for Safari does not. It names a browser and leaves the constraint hidden.

Put documentation where its reader needs it

Some knowledge genuinely belongs in prose. Local setup, incident recovery, cross-module architecture, and design tradeoffs do not fit cleanly into a type signature.

The answer is still not a breadcrumb file. Patch the maintained surface that already owns the reader's need.

Diátaxis separates documentation into four forms because they solve different problems: tutorials teach, how-to guides help someone complete a task, reference supplies lookup facts, and explanation provides context and reasons.6 A missing recovery command belongs in the runbook. A configuration field belongs in reference. A cross-cutting tradeoff belongs in explanation.

Architecture Decision Records have an even higher threshold. Michael Nygard's original proposal limits them to decisions that affect structure, non-functional characteristics, dependencies, interfaces, or construction techniques.7 A local rename does not earn an ADR. Neither does every surprising line. If the repository already records significant decisions, use that system for significant decisions. Do not introduce one as incidental cleanup.

This placement rule matters because documentation can recreate the exact problem it was meant to solve. Put transaction requirements in a general agent-notes file and you now have two interfaces to saveOrder: the function signature and a document somewhere else. They will disagree eventually.

Self-healing needs brakes

"Leave it better than you found it" is pleasant advice and dangerous authorization. An agent can use it to justify renaming half a package during a bug fix.

Breadcrumbs applies two tests before editing:

  • Repeatability: could another competent developer reasonably hit the same confusion?
  • Payoff: would the repair shorten that trace without requiring knowledge of the skill?

Then it checks confidence and blast radius. A verified stale comment beside the code is safe to correct. A local error can usually gain context. A public API rename, data migration, architecture shift, or security-sensitive behavior change must not hide inside incidental healing. Surface the evidence and proposed repair instead.

Zero repairs is a successful outcome when the trace exposed no real smell. The skill rewards reduced future friction, not visible activity.

This restraint is what keeps a self-healing practice from becoming a self-rewriting codebase.

The skill

The skill packages the short healing loop in SKILL.md and keeps the deeper judgment in a linked Open Knowledge Format bundle: a smell catalog, repair patterns, autonomy rails, worked examples, and the research behind them. An agent loads the catalog only when it needs to diagnose an ambiguous trace.

Install it from the saschb2b/skills repository:

bash
npx skills@latest add saschb2b/skills --skill breadcrumbs

It can trigger from ordinary work:

text
We found the retry delay under analytics config after checking four
unrelated modules. Fix the value and make its ownership clearer.

Or from the moment the smell becomes visible:

text
I had to inspect every caller to learn that saveOrder needs an existing
transaction. Make that contract harder to miss.

The important part is what the skill refuses to produce. There is no breadcrumb registry, investigation log, freshness protocol, or requirement to read a special file before debugging. It makes one coherent repair, verifies it, mentions the incidental change in the normal handoff, and returns to the task.

You can read the full breadcrumbs skill, including its smell catalog and worked examples.

Close the trace

Every non-trivial investigation ends with a brief window in which you understand both the system and the friction that hid it. That window closes quickly. Use it.

Ask one question before you leave the area:

If the answer is "nothing, I was learning the domain," finish the task. If the answer is a misleading name, an invisible precondition, a dead path, a vague error, or a stale instruction, repair that one thing.

The next trace starts from a better codebase.


S
Written by
Sascha Becker
More articles