Acceptance gaps
Your monitoring tool has 33 tests. Every one of them passes. It has also never executed, not once, and nothing in your project will ever tell you.
Correct, complete, and never started
We built a tool that scans four public sources for conversations where our product belongs. It reads only. It never posts.
The acceptance was thorough. 33 checks in total. No write access, no credentials, results filtered for relevance, duplicates removed. A hard cap on output, back off on HTTP 429, and a dead source must not swallow the other three.
All 33 passed. The tool shipped. The task promised a list of conversations every morning.
There was no schedule. So there was never a list. And a tool that does nothing also reports nothing, so nobody noticed.
Why your test suite cannot see this
Tests answer questions about behaviour. Given this input, does the code do the right thing?
Whether anything ever supplies that input is a different kind of question. It lives in a scheduler, a workflow file, a systemd timer, a queue consumer. Your test suite has no opinion about it.
This is why the gap survives review. Reviewers read the diff, and the diff is correct. The missing part is not in the diff at all.
Note how ordinary the failure is. Nobody was careless. The work was good. It just was not connected to anything.
The question to add to every acceptance
What starts this, and how would I know if it stopped?
Ask it about every tool you ship that is meant to run on its own. A report, a backup, a sync, a scanner, a cleanup job.
It has two halves and both matter. Something must start it. And when it stops starting, that must be visible without anyone going to look.
Make it answerable by a machine
A question you have to remember to ask gets forgotten. So we wrote a guard that asks it for every tool at once.
The rule: any script whose own header says it runs daily must appear in a workflow file that has a schedule. Twelve lines of code, and it covers every tool we will ever add.
The list of daily tools is not maintained by hand. It is derived from the scripts themselves. A hand-maintained list is the same failure one level up, and it drifts the moment somebody forgets to update it.
A tool that genuinely needs no schedule writes the reason into its own file. The guard prints that reason instead of hiding it, so an exemption stays visible to the next reader.
Silence is the part that must change
Our scheduled job now sends a message every single day. Also on the days it finds nothing at all.
That looks like noise until you consider the alternative. A silent run is indistinguishable from a job that no longer exists.
One line a day is a cheap price for that distinction. If you find it annoying, you will notice the day it stops arriving, which is exactly the point.
What it cost to find, and what it costs to prevent
Finding it took an afternoon and a coincidence. Somebody asked what the tool had turned up, and the answer was nothing, ever.
Preventing it takes one guard and one habit. The guard runs on every push and takes under a second.
Write the guard on the day you find the gap. On that day you know exactly what to check for, and a week later you will not.
I build cachly — persistent memory for AI coding assistants, over MCP. ChatGPT and Claude remember your conversations. cachly remembers your codebase: the bug you fixed, why you chose Postgres, the deploy step that always breaks — including what your teammates learned. And every assistant you use reads the same memory.
Stop shipping tools that nobody starts — and give your assistant a memory of what your team already learned the hard way. Free tier, hosted in the EU: cachly.dev
Top comments (1)
That acceptance question is excellent. I’d turn the daily message into a durable expected-execution ledger so “ran and found zero” is mechanically distinct from “never ran.” For each logical period, record
due_at, scheduler dispatch, worker start, completion outcome, attempt, config/artifact digest, andnext_due_at; make the period key unique so retries, DST shifts, scheduler backfill, and overlapping workers cannot produce two logical runs. Alert on a missing transition after a deadline, not merely on an error emitted by code that may never start. The static guard proves a scheduled declaration exists, but I’d add a deployed canary too: enumerate the resolved workflow, invoke it in a non-destructive mode, and verify the same runtime identity, secrets, network path, and result sink used in production. Otherwise a renamed script, disabled workflow, permission regression, or dead notification channel can still pass the repository check. A heartbeat should be evidence from the actual execution path, not just another promise in source control.