◉ANTIGRAVITY LABJP
Articles/Agents & Manager
◈ Agents & Manager/2026-10-24Advanced

I Added pipefail and a Passing Check Turned Red: The False Failure That Starts Once Output Passes 8 KB

Adding pipefail can turn a passing check red. I reproduced the false failure that appears once output passes 8 KB on 1,122 files, then built a file-based runner and a canary.

Antigravity398bash3exit codepipefailpreflight3agent operations9

✦ Premium Article

In an earlier post I wrote about how a pipe can erase a failure. The remedy I gave was set -o pipefail.

This time I applied that remedy to the front-matter check for my own site and tested it again. The medicine, it turned out, has a side effect.

On a repository with no defects at all, the check went red. Nothing in the check had changed. Only the amount of output had.

A pipe can get an exit code wrong in two directions: it can hide a failure, or it can turn a success into one. The first is well known. The second shows up only on the day your inputs grow, which is why I found it easy to miss. Below are the measurements and the full text of the runner I ended up with.

The check, and the environment I used

The target is the MDX of the 1,122 Japanese articles on this site, which I run as an indie developer. The check looks at three things in the front matter: a misspelled level, a period in slug, and an unquoted colon in highlights. Each one causes a real 404 or 500 in production.

Here is the whole script.

#!/usr/bin/env python3
"""MDX front-matter check. Prints each violation and exits 1 if there is any.
If there are zero target files, exit 2 so that a green that looked at nothing cannot happen."""
import re
import sys
import pathlib
 
LEVELS = {"beginner", "intermediate", "advanced"}
 
args = [a for a in sys.argv[1:] if not a.startswith("-")]
verbose = "-v" in sys.argv
root = pathlib.Path(args[0])
files = sorted(root.rglob("*.mdx"))
if not files:
    print(f"NOFILES {root}")
    sys.exit(2)
 
bad = 0
for p in files:
    text = p.read_text(encoding="utf-8")
    m = re.match(r"^---\n(.*?)\n---\n", text, re.S)
    if not m:
        print(f"NOFM  {p}")
        bad += 1
        continue
    fm = m.group(1)
    if verbose:
        print(f"OK    {p}")
    lv = re.search(r'^level:\s*"?([a-z]+)"?', fm, re.M)
    if not lv or lv.group(1) not in LEVELS:
        print(f"LEVEL {p}")
        bad += 1
    sl = re.search(r'^slug:\s*"([^"]+)"', fm, re.M)
    if sl and "." in sl.group(1):
        print(f"SLUG  {p}  ({sl.group(1)})")
        bad += 1
    for h in re.findall(r'^  - ([^"\n][^\n]*:\s[^\n]*)$', fm, re.M):
        print(f"COLON {p}  ({h[:30]})")
        bad += 1
 
print(f"checked={len(files)} violations={bad}")
sys.exit(1 if bad else 0)

It has three exit codes: 1 for any violation, 2 if there is not a single file to look at, and 0 otherwise.

I added the 2 because an empty directory would otherwise report "0 violations" and go green. Run on an empty directory, the script prints NOFILES and stops with 2. A green that looked at nothing is more dangerous than a red.

ItemValue
ShellGNU bash 5.1.16
Python3.10.12
Target1,122 Japanese articles (a copy of the real repository)
Result on the real repositorychecked=1122 violations=0 (exit code 0)
Run time0.13 s (identical across five runs)

Three planted defects, five ways to call the check

The real repository has no defects, so I could not see the check go red. In a copied directory I planted three on purpose: level set to intermediat, .v2 appended to a slug, and 手順: added to the start of a highlights item.

Against that same copy, only the way of calling changed.

How it was calledExit codeVerdict
Run directly1Reported
Trimmed with tail -10Lost
Same, then read PIPESTATUS1 and 0Reported if you read it
set -o pipefail plus tail -11Reported
tail -1 || true0Lost (on purpose)

So far this matches the earlier post. With three violations present, looking only at the tail gives a green, and pipefail brings the 1 back.

I thought that settled it. Then I tried to carry the same pattern into another call, and tripped.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You can now tell, with a few lines in your own shell, whether a piped check is hiding a failure or turning a success into one
✦You can now prevent the false red that appears the day a check's output grows, using a file-based runner, before you start hunting for a cause
✦You can now confirm in one command that every new check really goes red on a known-bad input, so a green result stays trustworthy
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ Agents & Manager2026-10-22
17 Dead References in My Handover Docs Turned Out to Be 2 Files, and the Real Risk Was a Different Site's Playbook Mixed In
The full Python script I use to check the root .md files an agent reads. My naive version counted 17 dead references; fixed, it was 7 across 2 files. Then it missed the real problem. Here is how I found it.
◈ Agents & Manager2026-10-16
Before Handing an Agent a .zip: Dropping .git Cut 29 MB to 15 MB, and 28 Secret Warnings Turned Out to Be Noise
Antigravity app v2.23.0 lets you attach a .zip. I packed my own repository to count what .git weighs and how many secret-scan warnings are false alarms, and I leave the full Python script that excludes, scans, and writes a manifest.
◈ Agents & Manager2026-10-14
When the App Reads an API Response by Position, I Put a Gate in Front of the Upload So Shipped Versions Don't Crash at Launch
A client that reads a server list by index, as a wallpaper app does, leaves old shipped versions in the wild for a long time. Deleting or reordering a field can crash them at launch. This note shows an append-only check and a hash ledger that freezes shipped version directories, with the output from actually running them.