Every real TODO marker standing in my repositories was written on a single morning: 15 May 2026.
Four of them at 09:48:57 in one commit, the fifth seven minutes and thirty-six seconds later.
Today is that morning's one hundred and sixtieth day, and all five are still in place.
That wasn't the real story, though. I counted the history as well: across the entire history of
my five repos, 21 lines carrying a marker were added and 5 were removed. Among those five
removals there are two that genuinely closed, in the sense that the work they described got done
— and I wrote neither of them. Both came out of Flutter's project template.
What I scanned, and what I left out
Let me state the scope up front, because numbers like these are meaningless without one. I
scanned only my own projects: this blog's repository, köprü, vpsman, burncpu and gercekveri. I
never scanned the client repositories at all. Across these five, git tracks 8,090 files in total;
after the exclusions, the set I scanned is 2,021 files. On the history side there are 4,417
commits, merge commits excluded. I ran the measurement not in my local working copies but in
clean trees checked out from each repo's origin/main — a branch sitting a day behind will
quietly shift every denominator you publish.
The scanner is simple: it looks for the words TODO, FIXME, HACK and XXX on every line of
every tracked text file. What I left out: node_modules, dist, lock files, compiled assets and
third-party vendor directories. In the blog repo three more directories are excluded —
src/content/, public/ and src/assets/ — because that is published prose and assets, not
code. I counted that separately out of curiosity: of the 3,119 files under src/content/, 14
contain these words, on 16 lines in total. So what I excluded is not some vast pile; it is about
the same size as the 15 markers in my code. The reason for excluding it isn't the count, it's the
kind: the word "TODO" in an article is the subject matter, not technical debt.
Before trusting the scanner I tried to catch it lying. I wrote a small file holding three markers
that had to be found and three traps that had to be ignored: a todos variable, an autodoc
function, a TODO_LIST constant. It found the three and skipped the three. Skipping this step is
very tempting, but a text-scanning script has a habit of manufacturing its own findings.
Five of the fifteen hits are real
The raw result is 15 lines. Reading them by hand split them into three groups.
Six are placeholders: urn:li:person:XXX, token=XXX, ADR-XXX, and a me-veo-XXX inside a
filename template. None of them is debt; each is shorthand for "your value goes here."
Two are my own joke about TODOs. In the punchline list I wrote for the shorts pipeline, entry 26
reads: "WROTE A TODO. 2022. STILL TODO." The scanner reported my joke about technical debt as
technical debt — a measuring instrument that cannot recognise its own subject is a brutal summary
of everything that follows.
One isn't even code: a note in a speaker-proposal draft telling me to get a professional
photograph taken. And one is a line I never wrote — the stock TODO Flutter generates in the
CMakeLists.txt on vpsman's Windows side.
The remaining five are real, and all from one family: the four Remotion templates of the
shorts/video pipeline, plus generate-short.ts. All five open with the same phrase,
// TODO Phase 1:.
One small reconciliation gap turned up here. In the köprü repo the history showed two markers
while today's scan found one. I went looking for a deleted line; there wasn't one. The second
marker sits in a third-party CSS file under src/web/public/vendor/, and my scanner excludes
vendor directories by design. Both numbers are right — they answer different questions. Waving
a gap like that away as rounding is the easiest way to break a measurement.
Someone wrote this trap down in 2005
Placeholder XXXs inflating a scan is not a new discovery. PEP 350, "Codetags",
written in the Python community in 2005, discusses this exact word: it gives markers like TODO,
FIXME, HACK and BUG the collective name codetag, and it does not treat XXX as a
canonical tag — it lists it among the synonyms of FIXME. One of its reasons describes my six
false positives precisely: XXX is a useful placeholder where a value is unknown, which is why
FIXME is the preferred spelling.
The PEP's real proposal is more ambitious: put the answers to who wrote this, on what date, with
what due date and what priority next to every marker. Its suggested form looks like
<MDE 2005-09-04 d:14w p:2>. My five TODOs carry none of those fields — no date, no priority, no
owner. The odd part is that the PEP itself was never adopted; its page opens with "This PEP has
been rejected," on the grounds that there was no desire to make the standard library conform to
it. The industry has known about writing a date next to the marker for two decades, and has
declined to do it for two decades. So have I.
Three scanners, three different numbers
I would like to treat my own grep as the measurement, but the result changed beyond recognition
as I moved the settings.
First I turned off case sensitivity: 15 hits became 30. All 15 newcomers were noise —
ADMIN_API_TOKEN=xxx examples, a const todo = rows.filter(...) variable, some xxx-form.tsx
filename templates in a CLAUDE.md. Then I made the term list resemble Visual Studio's task list;
in that environment HACK, TODO, UNDONE and UnresolvedMergeConflict are predefined, and the
documentation states those token labels are case insensitive. Adding UNDONE took it to 31: one
new line, the phrase "escapes are undone" in a Rust comment. I changed the two settings in two
separate steps; flipping both at once and then blaming case sensitivity alone would have been
slander against my own measurement.
Then I went to the other end of the scale. ESLint's no-warning-comments rule exists to do
exactly this job, and its documented defaults read as follows: the terms searched for are
["todo", "fixme", "xxx"], matching is case-insensitive and whole-word, and the location is
"start" — the term must sit at the beginning of the comment. In the rule's own source the
recommended field is false, so it ships switched off in every preset.
I couldn't run that rule, because ESLint isn't installed in the blog repo where the five TODOs
live. Three of my repos do have it — two mobile sub-apps and the root of gercekveri — but none of
the markers sits within those three. So rather than the rule, I rebuilt its documented default in
a script of my own and limited it to the file family ESLint would look at: 831 JS/TS files, 5
reports. The same five real TODOs.
This does not mean the tool is smarter than me; part of why ESLint looks precise is that its scope
is narrow. It never looks at markdown, CSS, Rust or Gradle files, and most of my noise was sitting
in exactly those. Visual Studio's four tokens are themselves defined for C/C++, C# and Visual
Basic, while I applied them to every file type. Still, one lesson is clean: requiring the marker
to sit at the start of the comment wipes out the whole placeholder class on its own.
All of them inside seven and a half minutes
I asked git blame for the age of the five. The four templates were written at 09:48:57 in a
single commit, the fifth — generate-short.ts — at 09:56:33. Seven minutes and thirty-six seconds
between them. Today is 22 October; 160 days have passed since that morning, and to these five
standing markers nothing has been added and nothing has closed since. (The rest of the history
does move; I'll count that shortly.)
The date says something on its own. Those two commits are the commits in which the shorts pipeline
was born. The moment I wrote the markers was the moment the pipeline first ran, and I put the
intention to prettify it into the code as a note. The minute you finish the first version of
something is the minute you hold the most optimistic possible estimate of your future energy.
Did "Phase 1" happen somewhere else instead? No — and rather than assume it, I went and counted.
The contents of the four marker-bearing templates have not changed since the day they were born;
after the two commits of that morning, the only commit to land in remotion/ is a version bump on
28 September. Meanwhile the pipeline is live: the repo holds 20 content files for 10 short videos
across Turkish and English, and the templates are called from Composition.tsx. What I left
undone never stopped the pipeline from working. Which is the real finding: those markers were not
a record of debt, they were a wish.
Twenty-one markers, five deletions
The current state is a snapshot; the history was what interested me. Across the full history of
the five repos I counted lines containing a marker being added and removed: 21 lines added, 5
removed. The honest denominator here is 21, not the commit count — a line that was never written
cannot be deleted. Opening those five deletions one at a time turned out to be the whole point.
Two are genuine closures, and I wrote neither: the build.gradle.kts file on vpsman's Android
side carried two stock TODOs generated by Flutter — "specify your own unique application ID" and
"add your own signing config for the release build." I really did both; one on 8 May, the other on
29 August.
One is fake. On 1 June I rewrote the header comment of unsubscribe.ts; the same commit deleted
one line and added a near-identical one. The counters show an add and a delete while nothing
closed. If you don't filter out add-and-delete pairs inside a single commit, this measurement
inflates in both directions at once.
One fell off. On the morning of 16 May at 10:43 I wrote a line into burncpu's README: "local
Docker: (TODO)". At 11:14, thirty-one minutes later, a commit reorganising the README took that
line with it. The marker went — so the work was done? No: the local Docker instructions landed on
14 July. There are 59 days between the death of the marker and the completion of the work, and no
causal link whatsoever between them.
One was solved by another route. On 15 May I established that video upload to X wasn't working and
wrote this next to it: "or implement Twitter v1.1 chunked upload directly (TODO)". On 11 June I
fixed the problem — but not that way. I wrote a patch for the library I was using, adding the
missing parameters and the status polling. When the fix arrived, the whole paragraph containing
the TODO was deleted. The problem closed; the plan the marker proposed was never carried out.
The denominator is small, so let me put it on the table. Eight TODOs written by my own hand mean
actual work: the five still standing, the photograph note in the speaker proposal, burncpu's README
line, and the X upload line. Only two of them have ever been deleted — the last two cases. So the
sentence "no TODO I wrote was ever closed by doing what it said" rests on two deletions; the other
six are still standing and haven't had their chance yet.
What the two closed TODOs have in common
To see whether a mechanism falls out of this, I counted how many commits touched the files
carrying the markers. The four templates that hold four of the five living TODOs have been touched
by one commit each since birth — the commit that created them. The fifth, generate-short.ts, got
two, both on the same day (one at 09:56, the other at 13:33). In those same 160 days that repo
took 2,759 commits, 1,208 of them under scripts/. The repo was working like mad; those five
files were never opened.
The file of the TODO that closed lives a different life: build.gradle.kts has taken four
commits, the most recent on 12 September. My first explanation was this: what gets a comment read
is not good intentions, it's the file being reopened for an unrelated reason.
But to be honest, three explanations come out of this data and two closures can't tell me which is
right. The second: at least one of the closed items blocked a path — a signing configuration is
required to ship to a store. (I can't claim the same for the application ID; I may have done that
because its turn came, not because it blocked me.) The third, and perhaps the strongest: both
closed markers were generated by a tool, and both sat immediately above a line I had to change
anyway while preparing that file for release. All three land in the same place: there is no such
thing as a self-reading comment.
The ledger lives outside the code
So where do my open items sit? If there are five lines in the code, where is the rest? I looked at
the notebook belonging to this project: 57 files, holding 22 lines that say "open item," "not
fixed yet," "waiting" or "remaining steps," spread across 12 of them. Those 12 files also carry 84
lines bearing a warning mark; across the whole notebook that mark appears on 116 lines, and most
of them are traps rather than open work. (That is one project's notebook alone; across every
project notebook on the machine there are 497 note files.)
Here's the interesting part: PEP 350's author argues the exact opposite. Answering objections, he
says he would rather have the information in one place — the source code — than in various other
documents likely to get duplicated or forgotten. My measurement partly inverts that sentence: the
thing that got forgotten was the marker in the source code. But his deeper point survives, because
what keeps my notebook alive isn't its format either — it's the habit of returning to it every
session. What makes a ledger a ledger is its reader.
When I audited 1,973 runs of my scheduled jobs
I found that no record was being kept. Here a record is kept but has no reader — the same
blindness I saw when I counted the scripts nobody calls.
In all three the mistake sits in the same place: I took a thing's existence for its working.
What I'm changing
The conclusion of this measurement is not "stop writing TODOs." All five markers told me
something; this article exists because I read them. What I'm changing is the question I ask while
writing one.
If I'm about to write a TODO, I now ask first: what force will get this line read? Will the file be
reopened for another reason, or does the work block a gate? If either is true, the marker earns
its place. If neither is, what I'm writing is a wish, and there is no point hiding a wish inside
the code.
Second: I put the marker at the start of the comment — one line of rule, and the entire
placeholder noise class is gone. Third: no undated TODOs. I don't need to make PEP 350's rejected
field list mandatory, but dropping a date next to the marker stops a hundred and sixty days from
passing unnoticed; today I had to run git blame to count those days.
I'll also keep my open-item ledger in prose, because the measurement showed that is the side which
actually gets read. I'll just give it the name it has earned: that is my issue tracker. The marker
inside the code is not a reminder, it's a decision — and writing a decision in the costume of a
comment is the polite way of not making it.
Top comments (2)
Bro if you are reading this please say me I should or not
so I'm about to add a new product in Akhouri Systems SNIPPE (Windows snipping tool killer)
but in same time I've to remove ANOTE & ACALCU because I can't focus on all products it will mess my mind
you are experienced pro level bro that's why I'm asking you and asking a suggestion I should or not
my freind effessdev said I should do it, hope you will respond fast because I've to take action fast.
Hey brother! ❤️🤝
First of all, I’m really sorry for replying so late. I’ve been dealing with a minor health issue over the past few days and had to take a little break. Thankfully, I’m feeling much better now! 😊
And thank you for trusting me enough to ask for my advice. That genuinely means a lot, my friend.
Here’s what I think: Yes, build SNIPPE, but don’t try to build everything at once.
One thing I’ve learned over the years is that having too many projects isn’t necessarily the problem. Trying to give all of them equal attention is.
If SNIPPE is the project you’re most excited about, give it a chance. But before removing ANOTE and ACALCU completely, consider archiving them. There’s no shame in putting a project on hold. You can always return to it later.
I’d suggest building a small, working version of SNIPPE first. Focus on what makes it genuinely different from existing screenshot tools. Let real users tell you whether it’s worth taking further.
And remember, brother, finishing one useful product is often more valuable than starting ten promising ones.
Whatever you decide, don’t put too much pressure on yourself. You’re building, experimenting, and learning. That’s already valuable.
I’m rooting for you, my friend! Keep building, but give yourself room to breathe too. ❤️🔥🤝