CI Passed. Production Failed. Here’s Why.
Passing CI is not proof of production readiness. CI validates syntax and test expectations. Production exposes runtime truth: env, wiring, auth, real APIs, real data, real load.
The 6 patterns that cause "CI green, prod broken"
1) Stubs that look real
A function returns a valid object, but it's fabricated or incomplete.
2) Mocked integration boundaries
Tests mock the database/client/auth layer so nothing real is exercised.
3) Missing configuration
Env variables are absent or misnamed, so code falls back to "safe defaults" (which are often fake).
4) Dead routes and placeholders
Routes exist but contain placeholder handlers or incomplete logic that never got hit.
5) Auth / RBAC isn't enforced
CI doesn't simulate real permissions and token paths.
6) Monorepo drift
Frontend calls endpoints that don't match the backend, and tests never cover the mismatch.
The fix: a deploy gate that checks reality, not just correctness
A practical gate checks:
- endpoint mapping (frontend ↔ backend)
- auth coverage (who can call what)
- mock/stub imports
- unused or placeholder route handlers
- required env vars present
One-command gate (example)
npx guardrail gateWhat "reality" looks like in a build
CI should fail on this:
- ✓ TypeScript compiled
- ✓ Unit tests passed
- ✗ API endpoints mismatch
- ✗ Mock client imported in production build
- ✗ Required env var missing: DATABASE_URL
If you want fewer production surprises
- Add a gate before deploy
- Block merges on "reality failures"
- Treat fake data and placeholder wiring as build-breaking defects