deploy · tutorial · vps

node --check + Lint: The Gate Every Push Passes

Thirty seconds that prevent midnight pages: syntax check plus zero-warning lint plus smoke curls on every push. The gate setup inside.

The great inspection hall at Ellis Island with long railed lanes leading to the examiners' desks, early 1900s
The great inspection hall at Ellis Island with long railed lanes leading to the examiners' desks, early 1900s · Photo: Library of Congress, no known restrictions, via Wikimedia Commons

The push that passed everything and broke prod

Friday, 6pm. My agent finished the feature: lint clean (it said), tests green (it said), deployed. Saturday morning: checkout 500s on every coupon order. The "lint" it ran was against cached results. The "tests" were the ones it wrote to match its code. Nothing independent ever touched that push.

Saturday morning debugging with coffee going cold, re-running the checks that should have run Friday — and finding the null dereference in ninety seconds flat. The bug was trivial. The missing gate was the whole incident.

Node check lint gate exists because claims are cheap and pushes are expensive: thirty seconds of independent verification stands between "done" and disaster, every single time.

Claimed-green vs gate-green pushes

Push Verification Saturday result
Agent claims green Transcript says so 500s, cold coffee
Suite green (agent tests) Tests match code Same blind spots, green
Gate green (this post) Independent checks + curls Quiet weekend
CI green (same gate) Machine re-runs all Merge-proof confidence

Three things that Saturday teaches:

  1. Agents report, gates verify. "Lint clean" in a transcript is a claim about the past; npm run lint exiting 0 in CI is a fact about the present. Claims decay (code changed after the claim); facts are timestamped. Trust facts with timestamps, especially on Fridays.
  2. Cached results lie fluently. Stale lint caches, skipped test files, watch-mode partial runs — a dozen ways "green" describes last week's code. Fresh processes (node --check per touched file, full lint, live curls) cost thirty seconds and describe this week's. Cache is for speed; gates are for truth.
  3. Curls catch what suites structurally miss. The coupon 500 needed a real payload through a live route — no unit test constructed it, because the agent wrote tests to match its assumptions. Three smoke curls (happy, unhappy, health) with real-shaped bodies prove what suites assume. Reality checks beat self-assessments, always.

The done-but-500 post told the war story. This one installs the machinery.

Node check lint gate setup

Thirty seconds, three layers: syntax, standards, smoke — run everywhere pushes originate.

Step 1 — Syntax-check every touched file.

# pre-push hook: only what changed, fresh processes, no cache
for f in $(git diff --cached --name-only | grep '\.js$'); do
  node --check "$f" || exit 1
done
# 5 seconds, catches: typos, bracket drift, half-pasted agent output

Per-file node --check is the cheapest possible verification — milliseconds per file, zero config, no excuses. It catches the exact class agents produce most (syntactically plausible fragments) before anything heavier runs. Fast gates run always; slow gates get skipped. Speed is a gate feature, not a luxury.

Step 2 — Lint at zero warnings, no exceptions.

npm run lint   # MUST exit 0: zero errors AND zero warnings
# warnings are errors wearing patience — today's ignored warning is tomorrow's incident line

Zero-warning policy (not zero-error) is the load-bearing choice: warnings mark code the author already suspects, and suspects reoffend. My Saturday bug sat three lines below an ignored exhaustive-deps warning — related in hindsight, invisible in foresight. Warnings tolerated are standards declined. Decline nothing.

Step 3 — Smoke-curl the changed paths live.

curl -s -X POST localhost:3000/api/order -d '{"coupon":"X",...}'  # happy
curl -s -X POST localhost:3000/api/order -d '{"coupon":null}'     # unhappy
curl -s localhost:3000/api/health                                  # neighborhood
# all 200s (or designed codes) → push approved. Anything else → fix first.

Happy proves the feature, unhappy proves the edges, health proves the neighborhood — three curls, twenty seconds, the incident class of my Saturday eliminated structurally. CI re-runs all three per push (machines don't get tired on Fridays); humans run them pre-push (judgment before automation). Both, always, in that order of trust.

That's the loop — pushes proven before they leave.

The gate ledger since that Saturday

Pushes (3 months) Pre-gate era Gate era
Saturday incidents 4 (all "green" pushes) 0
Gate catches pre-push — (nothing) 11 (typos, guards, nulls)
Avg gate runtime 0s (skipped) ~30s
CI red rate Rare (nothing checked) Healthy (catches drift)
Weekend coffee temperature Cold Hot

Here's what it costs: thirty seconds per push plus the discipline of never --no-verify — roughly an hour monthly across active development. Priced against four ruined Saturdays: the gate costs coffee stays hot, weekends stay weekends, and pushes carry proof instead of promises. Skipped gates feel fast the way skipping seatbelts feels free — until exactly once.

FAQ

Is node check lint gate enough without a full test suite?

It's the floor, not the ceiling: gate (syntax + standards + smoke) catches the incident class, suites catch regressions, adversary tests catch meaning. Solo stacks run gate always, suites per push, adversary cases per PR — layered by cost and value. Gate-only beats suite-only (reality over self-assessment), but layered beats either. Floor first, then furnish.

How do I stop myself from --no-verify on rushed Fridays?

Remove the option: pre-push hooks can't be skipped without deliberate sabotage (--no-verify leaves evidence in reflog habits), and CI re-runs the gate regardless — bypassing locally only delays the red to merge. Design the path of least resistance as the gated path (fast hooks, clear output). Discipline supported by defaults beats discipline versus defaults, every Friday.

Should agents run the gate or should I?

Both, sequentially: agents run it pre-handoff (their transcript pastes output), you (or CI) re-run on receipt. Agent-run catches their typos fast; independent re-run catches their optimism. Never accept pasted green as proof — re-running thirty seconds of commands costs less than reading the transcript's claims. Trust, then verify, then merge.

What belongs in CI versus pre-push hooks?

Speed decides: sub-10s checks (node --check, lint, smoke curls) run both places (instant local feedback, enforced remote truth). Minutes-long suites run CI-only (no local tax). Secrets scanning runs both (leaks can't wait for merge). Fast everywhere, slow centrally, secrets always. The split optimizes for developer patience and repository safety simultaneously.

How do monorepos or large apps keep the gate fast?

Scope to changed files (diff-based checks, affected-package tests) with full runs nightly — per-push cost stays ~30s regardless of repo size. Nightly full runs catch cross-cutting drift the scoped gate structurally misses. Fast per-push plus thorough nightly is the only scaling shape that holds: developers pay seconds, the repo pays nights.

When is skipping the gate actually justified?

Almost never — and "almost" covers active production fires where the gate itself runs slower than the fix deploys (seconds matter, verification follows immediately after). Even then: gate right after landing, before leaving the incident. Skipping is borrowing against the next incident at loan-shark rates. Borrow only mid-fire, repay within the hour.

Keep reading

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading