The 10‑Second Preflight Script That Stopped My 'It Works on My Machine' Fridays (and the morning it refused to run)

I built a tiny repo‑local preflight script that checks ports, env vars, migrations, and third‑party stubs in 10 seconds — and the one morning it blocked a hotfix taught me the right defaults.

Written by: Arjun Malhotra

Close up of hands typing on a laptop with code visible on the screen
Photo by Brooke Cagle on Unsplash

It was Friday. I had a demo at 4:30 PM for a client in Mumbai and a heap of confidence — because everything worked on my laptop. At 4:25, the payments page failed to load. No errors in the UI, just a blank spinner. The cause: a local mock for the payments gateway hadn’t started; our frontend silently waited and timed out after the demo. I left the meeting with a vague promise to fix “it”, and a new personal rule: never assume my machine is ready.

I solved it not by more tests or prettier docs, but with a single, tiny tool I keep in every repo: preflight.sh. It’s a fast, opinionated checklist that runs in about 10 seconds and fails loudly. It checks the things that always go wrong for me — missing env vars, required local services, migrations applied, and whether I’m inadvertently hitting a remote API instead of a local stub. After a month, Friday demos stopped failing. After six months I learned where it shouldn’t be strict.

What preflight does (and why it’s quick) I designed preflight for the developer who launches 5 tabs, a million background services, and then wonders why a demo dies. It needs to be:

The actual checks are annoyingly simple:

I keep it POSIX shell (no heavy deps). A typical run looks like:

./scripts/preflight.sh

OK env: GOOGLE_CLIENT_ID OK port: postgres 5432 OK mock payments: http://localhost:9001/health WARNING migrations: pending 2, run ./scripts/migrate.sh FAIL remote payments: application is pointed at live gateway — aborting

The “FAIL remote payments” bit saved my demo: a bad config pointed the frontend at the live payments gateway when my mock crashed. Preflight blocks that and forces an explicit decision.

How I kept it useful and stopped it being annoying Making something that blocks developers is easy. Making something that prevents dumb mistakes without becoming overhead is the trick.

The morning it refused to run (and what I learned) Three months in, we shipped a critical hotfix at 2 AM. My preflight refused to run inside CI containers because the base image didn’t include nc or curl — tools my script relied on. The preflight’s failure aborted the CI job and blocked the hotfix while I scrambled to add the missing utilities to the image. I was annoyed. So were the on-call engineers.

That incident taught me two things:

Indian context that mattered Two small realities in my setup shaped what preflight became:

Honest tradeoffs A repo preflight adds another file to maintain. It’s tech debt: it can bit-rot, duplicate logic from other scripts, and occasionally block. We accepted that because the cost of a failed demo or shipping a config that hits live payments is higher than maintaining a 150‑line shell script. Still, it demands one small habit: review preflight changes in PRs like any other code. When it broke our midnight hotfix, that policy forced the change that fixed it.

One takeaway If you keep shipping demos that only work on your machine, build a short, repo‑local preflight that checks the actual failure modes — ports, envs, mocks — and make it fast and overrideable. The point isn’t to be perfect. It’s to make the common, dumb failures noisy enough to stop you before a demo or a deploy. I still get the occasional late‑night override. But they now come with a timestamp, a reason, and a brief follow‑up. That’s enough to make Fridays predictable.