Field guide

The returnable note

We read 25,546 workaround notes across 1,813 of the most-starred repositories on GitHub — probably more of them than anyone has read on purpose. About 1 in 20 can ever be acted on. The other nineteen are honest, correct, and permanently stuck. The difference is not diligence. It is three sentences.

25,546notes read
~1 in 20carry an address a machine can check
25%of repos wrote nothing at all
4.2 yrsmedian wait after the reason died

First, the important part: whoever writes any note at all did the right thing. The person who typed "workaround until #1234 is fixed" wrote the reason down and left an address, which is more than 25% of the census managed. What failed afterwards is that coming back was nobody's job. This page is about writing the note so that coming back is possible — for a teammate, for a machine, for us.

The anatomy — three sentences

A note someone can come back for answers three questions. Most notes in the census answer one.

1 · what it works around

Name the constraint, not the feeling

// hack tells a future reader you were unhappy. // Jinja strips the template's final newline tells them what has to still be true for this code to be needed. If the constraint is somebody else's bug, say whose.

2 · the removal condition

Say what would make it unnecessary

"Remove when we drop Python 3.7." "Delete once upstream releases the fix." One sentence turns a permanent apology into a scheduled departure. The census shows what happens without it: code that outlives the browser, the runtime, and sometimes the company it was written for.

3 · a machine-checkable address

Leave an address a machine can watch

This is the rare one — the ~1 in 20. An issue URL, a version bound, a date. // workaround for jgm/CommonMark#276 can be re-checked forever by anyone, including a watcher that never forgets. // remove when things improve upstream can be re-checked by no one.

The same note, three ways

// HACK: don't touch this

Grep finds it. Nobody can act on it. It will outlive you.

// Workaround: Jinja removes the final newline from templates.
// Remove once that's fixed upstream.

A human who already knows the story can act on it. A new teammate, or an agent, cannot — which upstream? fixed where?

// Workaround: Jinja strips the template's final newline
// (mitsuhiko/jinja2#75). Remove when our floor reaches a
// release containing the fix.

Anyone can act on it, today or in ten years. The issue is an address; the dependency floor is a fact a machine can compare. This is the note our watcher — or your own grep — can come back for.

A closed issue is still only testimony

One honest caution, because we learned it the hard way: the address makes the note investigable, not self-executing. In our own top-ten table, one cited issue was closed as completed in 2016 — and the library's shipped release contains no fix. Somebody clicked a button. The note's address is a lead; the lockfile and the shipped artifact are the evidence; a human reading the code in place makes the diagnosis. The report walks through what that discipline looks like.

Tools that already exist — use them, don't wait for ours

If your ecosystem has a convention for expiring notes, adopt it. We deliberately invent no rival syntax: the address matters, the punctuation doesn't.

eslint-plugin-unicorn ships expiring-todo-comments — TODOs that fail the lint when their date passes, their dependency ships, or their engine floor moves. todo_or_die (Ruby) raises at runtime when the condition you named comes true. Both make the removal condition executable. And a plain issue URL in a comment — no tooling at all — is already the 1 in 20.

If you run npx contextdebt, the scan reads these notes as written: markers whose cited issue is closed are flagged as leads for a human to judge — never as verdicts. Your code never leaves your machine.