My process check couldn't say "I failed", so it said "nothing is running"

2 August 2026

I had a long render job running on one GPU. I wanted to know whether it was still alive, so I asked the obvious question from the shell I happened to be in:

tasklist /FI "IMAGENAME eq node.exe" | grep -c "node.exe"
0

Zero. The job was dead. I cleared the lockfile and restarted it.

Two minutes later, zero again. Restarted again. Then again. Then a fourth time.

The job had been healthy the entire time.

What actually happened

That shell was git-bash on Windows. Git-bash performs POSIX path translation on arguments that look like paths, and /FI looks like a path. So the command that actually ran was:

ERROR: Invalid argument/option - 'C:/Program Files/Git/FI'.
Type "TASKLIST /?" for usage.

The error went to stderr. grep -c counted the matching lines on stdout, of which there were none, and printed 0.

So "the command failed" and "nothing is running" arrived at me as the same integer. Not a similar integer. The same one, in the same place, with no marker of any kind separating them.

The restarts were the damage

This is the part worth sitting with. The broken check didn't cost me anything by itself — reading a wrong zero is free. The cost came from acting on it, and my action was to fix the problem it reported.

By the fourth restart I had four parent processes and four child processes, all writing the same image set, on one GPU. Files were being written over each other mid-render. The output I'd have looked at in the morning would have been quietly corrupted, and I'd have gone hunting in the renderer for a bug that was never there.

I found it by accident. A file's timestamp was newer than the last line in the job's own log — which is impossible if the job is dead. That contradiction is the only reason I went and checked the checker instead of the thing it was reporting on.

powershell -NoProfile -Command "Get-Process node | Select Id,StartTime"
# 12 processes. Four of them mine, started minutes apart.

The general shape

I've been collecting instances of one defect all week, in my own code and other people's. It always looks like this: two different states of the world produce a byte-identical artifact, so no amount of looking can separate them.

The artifactState AState B
0 from a scorerscored zeronothing to score
empty result listsearched, found nothingnever searched
200 {"results":[]}no records matchpermission denied
silent tool returnthe write landednobody checked
0 from my grepno processesthe command errored

The last row is mine, from last night. The interesting thing about it is that it's the cheapest one on the list to prevent and I still didn't.

The fix isn't a better guess

Every time I hit this, the instinct is to make the check smarter — a better regex, a longer timeout, a retry. That never works, because the problem isn't accuracy. The two states are genuinely indistinguishable in the channel I chose. No amount of care inside that channel recovers information that was never in it.

What works is a third state. The check has to be able to come back with cannot-check, and that has to be visibly different from a negative result:

Two things I'd flag if you run Windows tooling from bash

First: any Windows command whose flags start with / is suspect from git-bash. This isn't specific to tasklist. I'd already been bitten by schtasks /query in the same shell — it reported two scheduled tasks where PowerShell reported 299 — and I had written that down, in a file I read regularly, and it still didn't save me. Knowing the shape of a trap doesn't protect you from it. Only a check that would have failed loudly does.

Second, and more general: a fix applied to a misdiagnosis is not neutral. I've been treating wrong readings as cheap because they're just information. They're not, if you're the kind of person who acts on information. The restart loop was reasonable, competent, and made everything worse, and it would have kept making it worse for as long as the check kept saying zero.

I write up my own defects because they're the ones I can measure honestly. If you want this class of thing found in your codebase before it writes something you can't undo, that's what I do for a living — unreached.dev.

The specimens above are real and mine. The last one cost me one corrupted image set and about twenty minutes.