Fixing the 'Tests Pass But Production Breaks' Dilemma
Hướng dẫn chi tiết về Fixing the 'Tests Pass But Production Breaks' Dilemma trong Vibe Coding dành cho None.
Fixing the ‘Tests Pass But Production Breaks’ Dilemma
It is the nightmare of every modern developer, especially those embracing the high-velocity world of Vibe Coding: your local environment shows a sea of green checkmarks. Your unit tests are passing, your linter is silent, and your AI assistant has assured you that the feature is “complete and verified.” You trigger the deployment pipeline with a sense of triumph, only to watch the production logs explode with 500 errors, “process.env is undefined,” or the dreaded “Script execution exceeded memory limits.”
In the era of AI-accelerated development, this gap—the space between “works on my machine” and “works for the users”—has become the single greatest bottleneck to true engineering autonomy. We call it the Epistemic Gap. It’s the difference between what your AI agent believes it has accomplished and what the production infrastructure actually requires.
If you have ever felt the dopamine hit of a successful test run followed immediately by the soul-crushing reality of a broken production site, this article is for you. We are going to deconstruct exactly why this happens and, more importantly, how to build a “Quality Gate” that makes your deployments as reliable as your code is fast.
The Vibe Coding Fallacy: Why Standard Tests Fail
In traditional development, we write tests to prove our logic is sound. In Vibe Coding, we use AI to generate the logic and the tests. This creates a circular dependency of trust. If the AI hallucinates a specific API behavior, it will likely hallucinate a test that validates that very same behavior. The tests pass, but the fundamental assumption is wrong.
There are three primary reasons why “Green Tests” don’t equal “Stable Production” in a Vibe Coding workflow:
- Environment Asymmetry: Your local Node.js environment is a loose, forgiving playground. Production environments—like Cloudflare Workers, Vercel Edge, or hardened Kubernetes pods—are strict, resource-constrained, and often use different runtimes (e.g., Workerd vs. Node).
- The Secret Silence: Tests often mock external services or use
.env.testfiles. Production fails because a secret wasn’t rotated, a variable was misspelled in the dashboard, or the AI didn’t realize that a specificprocess.envkey is required at build time. - The “Just-in-Time” Dependency Trap: AI agents frequently pull in the latest versions of libraries. If your local
node_modulesis slightly out of sync with your lockfile, or if a transitive dependency breaks in a way that only manifests in a bundled production build, your unit tests will never see it.
Core Concept: The Triple-Gate Protocol
To solve this, we move away from “Testing” and toward “Evidence-Based Verification.” At Todyle, we implement what we call the Triple-Gate Protocol. This is a sequence of automated checks that must be satisfied before an AI agent is even allowed to claim a task is finished.
Gate 1: The Integrity Gate (Static Validation)
This isn’t just about linting. This is about verifying that the code “fits” into the project’s existing puzzle.
- Type-Strictness: Running
tscon the entire project, not just the changed files. - Secret Scanning: Using a “Secret Shield” to ensure no API keys were accidentally hardcoded during the “vibing” session.
- Circular Dependency Check: Ensuring the AI hasn’t created a spaghetti-mess of imports that will crash the bundler.
Gate 2: The Build Gate (The Reality Check)
This is the most skipped step in Vibe Coding. Many developers let the CI (GitHub Actions, etc.) handle the build. This is a mistake. You should never push code that hasn’t successfully built into its production format locally.
- For a web app, this means running
npm run build. - For a Cloudflare Worker, this means running
wrangler build. If the minifier crashes or the tree-shaking removes a critical function, you want to know before the git commit.
Gate 3: The Behavioral Gate (Visual & E2E Verification)
Unit tests are blind. They check if add(1, 1) === 2. They don’t check if the “Submit” button is hidden behind the footer on mobile devices.
- Smoke Testing: Using a tool like Playwright to launch a local production build and verify that the main route returns a 200 status code.
- Visual Regression: Taking a screenshot of the new UI and comparing it against a baseline.
Practical Example: Building an Automated Quality Gate
Let’s look at how to implement a “Safety Script” that bridges the gap. Imagine you are building a feature for a Cody Master-powered project. Instead of just running vitest, you create a verify-feature.sh script that the AI agent must run.
#!/bin/bash
# verify-feature.sh - The Ultimate Vibe Coding Safety Net
set -e # Exit on any error
echo "🚀 Starting Quality Gate Verification..."
# 1. Static Analysis
echo "🔍 Checking Types and Lint..."
npm run lint
npx tsc --noEmit
# 2. Unit & Integration Tests
echo "🧪 Running Test Suite..."
npm run test:run # Ensures CI mode, no watch
# 3. Production Build Simulation
echo "🏗️ Simulating Production Build..."
npm run build
# 4. The 'Secret Shield' Check
echo "🛡️ Scanning for Secret Leaks..."
if grep -rE "sk-[a-zA-Z0-9]{32}|AIza[a-zA-Z0-9_-]{35}" ./src; then
echo "❌ ERROR: Potential Secret Leak Detected!"
exit 1
fi
# 5. Smoke Test (The 'Actual' Verification)
echo "💨 Running Production Smoke Test..."
# Start a local preview server in the background
npm run preview &
PID=$!
# Wait for server to be ready
sleep 5
# Use curl to verify the app isn't just a blank white screen
RESPONSE=$(curl -s -w "%{http_code}" http://localhost:4173 -o /dev/null)
if [ "$RESPONSE" -ne 200 ]; then
echo "❌ ERROR: Production build returned HTTP $RESPONSE"
kill $PID
exit 1
fi
echo "✅ All Gates Passed! Ready for Deployment."
kill $PID
Why this works
By forcing the AI agent to execute this script, you are teaching it that “passing tests” is only 25% of the job. The agent now has to contend with the bundler, the type-checker, and the network response of a minified build. This is where 90% of “Tests Pass but Prod Breaks” bugs are caught.
Interactive Workflow: The ‘Evidence Before Assertions’ Rule
In a high-functioning Vibe Coding environment, you should adopt a strict rule: No agent can claim success without providing a snippet of the validation output.
If an agent says: “I have fixed the login bug,” your response should be: “Show me the output of the production build and the smoke test logs.”
This forces the agent to use its tools (like run_shell_command) to actually verify the state of the world rather than just predicting what the code should do. Here is a real-world scenario of how a subagent-driven verification looks:
- Agent: “I’ve updated the database schema and adjusted the API routes.”
- User (Hint): “Verify the migration works against a local production build.”
- Agent (Action): Runs
npm run build && npm run drizzle-kit push. - Agent (Action): Realizes the build fails because a
Typein the frontend was using a now-deleted database column. - Agent (Correction): Fixes the frontend type before notifying the user.
This “Self-Correction Loop” is what separates a junior vibe-coder from a master.
Best Practices & Tips for Production-Ready Vibe Coding
To truly eliminate the dilemma, incorporate these strategies into your daily workflow:
1. Environment Parity (The Docker Fallacy)
While Docker helps, it often adds too much overhead for simple web apps. Instead, focus on Runtime Parity. If you deploy to Cloudflare, use Miniflare or Wrangler dev locally. Do not test your edge functions in a standard Node.js environment if you can avoid it.
2. Secret Hygiene and ‘Mocking the Real’
Never mock a database with an in-memory array for integration tests. Use a local containerized version of your actual database (Postgres, Redis, etc.). If the AI agent can’t see the real database schema, it will invent one. Using a local “Real-ish” environment ensures that your SQL queries and data types are validated against the actual engine that will run them in production.
3. The ‘Continuity’ File
Maintain a CONTINUITY.md or a similar context file that tracks previous deployment failures. If the production build broke last week because of a specific library version, put that in the context. AI agents have short memories; if you don’t document the “Gotchas,” the agent will walk right into the same trap tomorrow.
4. Automated PR Reviews (The Second Pair of Eyes)
Use a subagent whose only job is to find bugs in the first agent’s work. At Todyle, we often dispatch a find-bugs or code-reviewer agent. This agent doesn’t write code; it only critiques. It looks for edge cases, performance bottlenecks, and security flaws that the implementation agent might have missed in the rush to finish the task.
5. Defensive Variable Access
Stop using process.env.MY_SECRET. Start using a validation wrapper like Zod to parse your environment variables.
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(10),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = envSchema.parse(process.env);
If a variable is missing, the app will crash immediately with a clear error message during the “Build Gate” rather than failing silently in production when a user hits a specific button.
Conclusion: From Vibe and Pray to Vibe and Verify
The “Tests Pass But Production Breaks” dilemma isn’t an inevitable side effect of using AI. It is a symptom of a workflow that prioritizes velocity over verification.
By implementing the Triple-Gate Protocol, enforcing “Evidence Before Assertions,” and using subagents to audit implementation, you transform your development process. You move away from the “Vibe and Pray” model—where you hope the AI understood the deployment constraints—and into a disciplined, autonomous engineering machine.
Engineering excellence in 2026 isn’t about writing the code yourself; it’s about building the infrastructure of trust that allows your AI agents to build it for you safely. Close the Epistemic Gap, stop the silent failures, and start shipping with the confidence that “Green” actually means “Live.”