All writing
MCPClaude CodeTypeScriptOpen SourceDevOps

Everything Passed. Everything Was Broken.

March 4, 2026

Here's a list of things I shipped to production that I shouldn't have:

  • An OpenAI API key hardcoded in eas.json
  • An empty catch {} block that silently swallowed an authentication failure for three days
  • A missing package-lock.json that let npm install resolve different transitive dependencies in production — @clerk/testing bumped @clerk/shared and took down the whole site with 500s
  • A form with action="" that submitted to nowhere

None of these were caught by TypeScript. None were caught by ESLint. None were caught by my test suite. All of them made it to production and caused real problems.

The vibe coder problem

"Vibe coding" is what happens when you're building with AI. You describe what you want, Claude writes it, tests pass, you ship. The feedback loop is fast enough that you stop reading every line. You're working at a higher level of abstraction — intent, not implementation.

This is genuinely productive. I build things in hours that would have taken days. But it creates a specific failure mode: you ship things you haven't fully reviewed.

CI catches syntax errors. Linters catch style violations. Type checkers catch type mismatches. Tools like gitleaks and GitHub's secret scanning catch committed credentials — if you've set them up. ESLint's no-empty rule catches empty catch blocks — if it's enabled. The checks exist. The problem is that when you're vibe coding, you're not stopping to verify that every safety net is actually in place.

These aren't code quality problems. They're deployment hygiene problems. And they happen more often when you're moving fast with AI because you're moving fast.

78 checklist items from real incidents

After the fifth time I shipped something embarrassing, I started writing things down. Every time something broke in production that should have been caught before deploy, I added it to a checklist. Every item traces back to a real incident.

The checklist grew to 78 items across three phases — 27 universal checks plus stack-specific overlays for mobile, web, infrastructure, and CLI projects:

Pre-engineering — before you start coding. Did you read the existing code? Did you define what "done" looks like? Did you check if this function already exists somewhere?

Pre-testing — before you declare "tests pass." Does every exported function have a test? Are there empty catch blocks? Is your coverage number real or are you testing happy paths only?

Pre-deploy — before you push. Are there secrets in committed files? Is the lock file clean? Did you actually run the build? Do the environment variables in production match what the code expects?

The first version was a bash script. check.sh — 800 lines, auto-detects your stack (Expo? Next.js? Go? Docker?), runs the relevant checks, prints a report. It works.

The hook that blocks deploys

The checklist is advisory. The hook is enforcement.

Claude Code has a hook system — scripts that run before tool calls. I wrote a PreToolUse hook that intercepts every git push, vercel deploy, npm publish, eas build, and railway up. If you haven't run the pre-deploy checklist in the current session, the push is blocked.

Deploy blocked: pre-deploy checklist has not been run
in this session. Run /checklist pre-deploy first.

The hook checks for a marker file that the checklist writes on success. The marker includes a project hash (so you can't run the checklist on one project and deploy a different one) and a timestamp (markers older than 2 hours are rejected as stale).

This sounds aggressive. It is. But here's the thing: every single item on that checklist exists because I shipped something broken without checking it. The hook doesn't slow me down — it stops me from doing the thing I keep doing.

From bash to MCP

The bash script works for me. But it only works in my terminal, with my specific setup, triggered by my specific hook.

Deploy Gate is the MCP server version. Four tools:

  • run_checklist — runs checks against any project, any phase
  • check_deploy_ready — quick boolean: can this project deploy?
  • detect_stack — identifies what kind of project this is
  • list_checks — shows available checks, filterable by phase and stack

The MCP server means any AI coding tool can run your pre-deploy checklist. Claude Code, Claude Desktop, Cursor — they all get the same checks. The hook stays for enforcement. The server provides the checks.

What it actually catches

Seven automated checks ship in v0.1.0:

Secret scanning catches API keys, private keys, AWS credentials, and GitHub tokens in committed source files. The patterns are stored in an external JSON file because — and this is a fun bootstrap problem — the scanner's own source code contains the patterns it's scanning for. The first time I ran the checklist on Deploy Gate itself, it flagged its own secret-patterns as real secrets.

Empty catch detection finds catch {} and catch (e) {} blocks where errors are swallowed silently. This is the bug that cost me three days on CreateSocial — an auth failure was being caught and ignored.

Dead form detection finds action="" and href="#" in source files. Placeholder values that made it to production.

Lock file verification confirms your package-lock.json or equivalent exists and is committed. Without it, npm install resolves transitive dependencies at whatever version is latest — which is how a @clerk/testing update bumped @clerk/shared and caused site-wide 500s on CreateSocial.

Test coverage mapping checks that every source file has a corresponding test file. This one comes from the devreap build, where 40% test coverage was presented as "all tests pass" because nobody mapped files to tests.

Git clean check verifies no uncommitted changes before deploy.

Go vet runs go vet on Go projects to catch issues the compiler misses.

Each check includes a from field documenting which real incident created it. PD-3: "LooksMaxx: API key in eas.json". PT-5: "CreateSocial: silent auth failure from empty catch". The checklist isn't theoretical — it's a scar tissue registry.

The uncomfortable truth

I'm an experienced developer. I've been writing code professionally for years. I know you shouldn't commit API keys. I know empty catch blocks are dangerous. I know you should run the build before deploying.

I did all of these things anyway. Multiple times.

The uncomfortable truth about vibe coding is that the speed creates a false sense of security. Tests pass. Types check. The code looks right. So you ship it. And the thing that's broken is never the code — it's the stuff around the code. The config file you didn't review. The environment variable you forgot to set. The catch block that Claude generated and you didn't question.

Deploy Gate exists because I don't trust myself to remember 78 things before every deploy. The machine remembers for me.

42 tests. 7 automated checks. Works with any MCP client.

npx @iteachyouai/deploy-gate

Repo: github.com/ITeachYouAI/deploy-gate