False Green
I built six checks and wired them into CI. Later I found that each one reported success for work that never ran. Each one exited green.
The checks, the fixes, and the decision records are in this site’s own repository. It is public: github.com/sanlee-ys/portfolio.
Six greens. Each check reported PASS for work that never ran.
Two ways to fail
The first agentic review lane I put into CI broke three times before it worked.
- A missing OIDC permission Red in minutes.
- A missing tool grant Green in thirty-six seconds.
- A pull request that edited the lane’s own workflow file Green. The job never ran at all.
Call that second break run A. Run A spent $0.14 across seven turns. The
agent read the diff, formed a review, and attempted to post it six times.
Every attempt was refused. The agent exited zero. The result message
carried the array permission_denials with six entries. A tool
denial is not a job failure, so the job did not fail.
Same empty middle, opposite report. A deterministic tool fails when it does nothing. An agentic or hand-written gate can still report PASS, whether the work happened, was denied, crashed, or was never attempted.
I wrote that down as a lesson about agents. The same shape already sat in gates that I wrote by hand. It took another four months to find them.
The check that measured the wrong thing
A doubled rule marks a decision that was reversed.
Reversed. The gate that tested an invisible page
Mobile overflow, measured on unstyled pages
The mobile QA gate renders every page at four widths and fails on
horizontal overflow. It opened the built files directly, over
file: URLs. The layout emits root-absolute asset paths, and
those paths do not resolve over file:. So every page it
measured rendered unstyled.
The result: an unstyled page does not overflow. The gate ran and produced numbers for sixteen pages at four widths. The numbers described a page no visitor would ever see. It now serves the build over HTTP.
Reversed. The proof that proved something else
“Fonts embedded: Geist”
The résumé PDF script printed that line and I believed it
for months. It read the value from document.fonts. That
value proves a woff2 loaded into the browser. It says
nothing about the bytes written to the PDF.
The result: it read as evidence. It cost me a round of investigation into a font defect that did not exist. It now asserts against the raw bytes of the file it just wrote.
Both produced output. A crash is obvious. These numbers looked like evidence.
The check that could not see what it was looking for
Reversed. Every deep link, unchecked
The link checker split the fragment off first
One line: split the URL on #, split on ?, then
ask whether the file exists. So a link to a real page with a dead anchor
was a good link. The reader lands at the top of the page with no sign
that anything went wrong.
The result: the checker did this from the day I wrote it. So it reported every deep link on the site as valid, and it never checked one anchor. It mattered the moment I moved sections between pages. That was the three pull requests immediately before I found it.
Reversed. A face the gate cannot see
The font gate’s regular expression
The coverage gate matches font declarations with
@font-face\s*{([^}]*)}. A comment between
@font-face and its opening brace hides the block from
\s*. A closing brace inside a comment truncates the body
early.
The result: either way the face falls out of the scan
through a silent continue. The expected face count then
treats that gap as a pass.
Against the fixture I wrote afterward, the old pattern sees one face out
of three.
What each one was actually measuring
| The check | Appeared to measure | Actually measured |
|---|---|---|
| Mobile overflow | Every page at four widths | Unstyled pages, which cannot overflow |
| Résumé fonts | Fonts embedded in the PDF | Fonts loaded into the browser |
| Link checker | Every link resolves | Every link’s page exists |
| Font coverage | Every declared face | The faces its pattern could see |
| Review lane | A code review happened | The job exited zero |
| Denial counter | Tool denials in the run | A key that does not exist |
The check watching the check
Reversed. The instrument that read an absent field
A counter that was structurally always zero
I built the review workflow to warn me about exactly the failure that started this page. If a run hit tool denials, the classify step would report it. Then nobody could ever again read a green check with no comment as a clean review.
The log is the raw message stream. Its result message carries the array
permission_denials. My counter read
permission_denials_count, which is not a key on that
message. The action derives that count for its own stdout summary only.
A read of an absent key returns undefined, and the code defaulted that
to zero.
Run B is a later run, on this site’s own review lane. Its classify
step logged denials=0. It reported turns as 8
and cost as 0.1447. Both figures matched the run summary to
the digit. The same result object, and one field silently misread. The
turn, cost, and denial figures on this page belong to two different
runs.
The result: in run B the review agent held four tools, and none of them could open a file. The diff’s meaning lived in the markup. The agent burned five denied reads and posted nothing. The instrument I built to report those denials read a field that was never there. The two defects concealed each other.
The lane’s earlier configuration was worse. For ten days it had no publish channel at all. It posted zero comments on zero pull requests. That looked like a quiet stretch with nothing to say.
The question
Each defect is one line. A passing check and a check that cannot fail look the same from the outside. I found each one. I asked a specific question of a specific gate.
What is this green measuring → and can it tell “passed” from “never ran”?
The cheap version reconciles the gate’s own reported count against a raw grep, once. Every gate on this site now asserts a non-zero count of the thing it walked. A gate pointed at an empty directory finds nothing and reports that as success.
One of them did not, and this page said so first. The metrics gate skipped its parity check on Markdown. So a Markdown marker that parsed to nothing looked exactly like a file that makes no claims. A wrong mark on a number was quieter than no mark at all. This page named that gap while it was still live.
It is closed now. The Markdown branch counts its own markers. A suite asserts that the counter reports a gap for each shape the value pattern cannot read. A parity counter that returns zero and a correct parity counter produce identical output on a clean tree. So the fix was unfalsifiable until something made it fail on purpose. If I revert the one-line exemption, exactly three of those tests turn red. The suite is scripts/check-published-metrics.test.cjs.