Field guide
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.
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.
A note someone can come back for answers three questions. Most notes in the census answer one.
// 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.
"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.
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.
// 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.
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.
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.