On this page#
- The straight answer
- The evidence
- The caveats
- What to do about it
- Invariants
- Change rules
- Verification
- Related questions people actually ask
The straight answer#
Is vibe coding bad? Yes, when the agent’s output becomes the source of truth. No, when it is a fast implementation layer beneath explicit invariants, review, and automated checks. The deciding factor is simple: can you independently verify that the change preserves the repository’s contract, or are you trusting code because it runs?
Vibe coding means producing software through natural-language interaction with an AI while paying limited attention to the generated code itself. That last condition matters more than the tool, model, or prompt.
Using Claude Code, Codex, or Cursor is not automatically vibe coding. You can delegate implementation without delegating judgment.
For a production repository, the boundary is clear:
| Practice | Acceptable agent-assisted engineering | Bad vibe coding | |---|---|---| | Requirements | States invariants and acceptance criteria | Describes the desired appearance | | Context | Gives the agent repository rules | Assumes the agent will infer them | | Validation | Runs targeted tests and broader checks | Stops when the demo works | | Review | Examines behavior, boundaries, and diff | Skims for obvious syntax problems | | Failure response | Finds the violated assumption | Adds prompts until the error disappears | | Ownership | Engineer can explain the change | Agent output is effectively unaudited |
The bad version optimizes for visible success. Real repositories require preserved behavior, compatible interfaces, safe failure modes, and maintainable structure. “It works” proves only that one path worked once.
Photo by Leeloo The First on Pexels.
The evidence#
AI coding agents generate plausible implementations from context; they do not acquire responsibility for your system’s invariants. A fluent diff can still weaken authorization, duplicate an existing abstraction, mishandle retries, or silently change an interface. That failure mode follows directly from how the system generates code, not from whether its output looks professional.
A large language model predicts and generates language—including code—from patterns and supplied context. It can reason usefully, but it does not possess an independent, complete model of your repository unless that model is present in the context and reinforced by feedback.
This creates three recurring failures.
Local correctness can violate global behavior#
An agent can produce a locally reasonable function that is globally wrong because repository invariants often live outside the edited file. The relevant rule may exist in a caller, migration, queue consumer, deployment assumption, or undocumented operational constraint. Passing the nearest unit test does not establish system-level compatibility.
Suppose an agent changes a write operation to retry automatically. The code may be clean, typed, and tested. If the operation is not idempotent, the retry can duplicate effects.
The missing fact was not syntax. It was an invariant: “This side effect must occur at most once.”
That is why context must name constraints before implementation begins. Official Claude Code documentation explains the tool’s supported workflows and controls, but no product documentation can enumerate the private contracts inside your repository.
Plausibility hides uncertainty#
Generated code rarely marks every assumption it made, so uncertainty arrives disguised as a finished implementation. The agent may choose a familiar pattern even when the repository uses a different convention for good reasons. The danger is not messy output; it is polished output whose hidden assumptions never become review questions.
Typical hidden assumptions include:
- A missing record should return
null, not raise an error. - A request may safely be retried.
- A field is optional because its type permits absence.
- A transaction boundary belongs inside the new function.
- An existing helper is obsolete rather than intentionally specialized.
- A test fixture represents production data.
More prompting does not automatically resolve this. A confident explanation is still generated output. Verification must come from repository evidence: contracts, callers, tests, schemas, and observable behavior.
Speed can move work downstream#
Vibe coding feels fast when implementation time is measured and comprehension, review, and repair time are ignored. A large generated diff may save typing while increasing the cost of proving correctness. The engineer then pays the difference during review—or the team pays it later during an incident.
The useful metric is not “time until code exists.” It is time until the change is understood, validated, reviewable, and safe to merge.
A 500-line solution is not efficient if a 70-line change would preserve the same invariant. Agents are excellent at producing code, so they need explicit pressure to minimize surface area.
PairFoundry’s foundations are useful here because reliable agent work starts before the prompt: define the contract, constrain the change, and decide how success will be proven.
The caveats#
The answer changes when the cost of being wrong changes. Disposable exploration can tolerate weak verification; authentication, payments, migrations, concurrency, and destructive operations cannot. The same workflow that is sensible for a throwaway prototype becomes irresponsible when code touches persistent data, security boundaries, or interfaces owned by other systems.
Vibe coding is defensible when all of these are true:
- The artifact is disposable.
- Failure is obvious and cheap.
- No sensitive data or privileged operation is involved.
- No one will mistake the result for production-ready code.
- Rebuilding from scratch is acceptable.
It is the wrong method when any of these are true:
- The change modifies authorization or access control.
- It writes, deletes, migrates, or transforms persistent data.
- It alters a public API, event schema, or shared library.
- Correctness depends on concurrency, ordering, retries, or time.
- The repository contains compatibility guarantees.
- The reviewer cannot explain the generated diff.
Agent assistance remains useful in those areas, but the workflow must become stricter. The Claude Code documentation can tell you how to operate the agent; your repository must tell it what must never change.
Photo by Lukas Blazek on Pexels.
What to do about it#
Keep the agents, but replace vibe-based acceptance with contract-based acceptance. Configure repository context once, make every task state its invariants, force the agent to expose assumptions, and require evidence before accepting a diff. The goal is not less AI-generated code. It is less unverified code, regardless of who typed it.
1. Put durable rules in a repository-level instruction file#
Store stable constraints where the coding agent will receive them repeatedly, not in a prompt you hope someone remembers. Keep the file short enough to remain salient and specific enough to reject a wrong implementation. It should describe architectural boundaries, validation commands, forbidden changes, and repository-specific definitions of done.
A practical structure is:
## Invariants
- Authorization decisions remain in the policy layer.
- Write operations must not gain automatic retries unless idempotency is proven.
- Public response shapes must remain backward compatible.

*Photo by Digital Buggu on [Pexels](https://www.pexels.com/photo/monitor-displaying-computer-application-374559/).*
## Change rules
- Prefer the smallest diff that satisfies the request.
- Reuse existing abstractions before adding new ones.
- Do not modify migrations or generated files without explicit approval.
## Verification
- Run the narrowest relevant tests first.
- Run the repository’s required checks before completion.
- Report unverified assumptions and skipped checks.Treat this as engineering configuration, not motivational prose.
2. Use a contract-shaped task prompt#
A strong task prompt defines observable success and protected behavior before requesting implementation. It separates requirements from suggestions so the agent knows what is fixed and where it may choose. This reduces accidental scope expansion and gives the reviewer a concrete standard for rejecting an otherwise convincing diff.
Use this structure:
Goal:
Current behavior:
Required behavior:
Invariants that must remain true:
Files or components in scope:
Explicitly out of scope:
Acceptance checks:
Questions to resolve before editing:The PairFoundry packs provide a useful way to standardize this kind of repeatable workflow across tasks rather than reconstructing it in every chat.
3. Make the agent review its assumptions before the diff#
Ask for assumptions and risk boundaries before accepting implementation, especially when the task touches data, permissions, retries, or interfaces. This does not make the agent authoritative. It turns silent guesses into inspectable claims that you can compare against the repository before those guesses harden into code.
Useful questions include:
- Which repository invariants does this change rely on?
- Which callers or consumers could observe different behavior?
- What assumption would make this implementation unsafe?
- What is the smallest viable diff?
- Which acceptance condition is not covered by an automated check?
If the answers are vague, implementation is premature.
4. Review behavior, then code#
Start review with the contract and the changed behavior, not line-by-line style comments. Confirm that protected invariants remain true, inspect boundary cases, and verify the evidence the agent supplied. Only then evaluate structure and readability. A clean abstraction cannot rescue a change that implements the wrong contract.
Reject the change when:
- The agent cannot identify affected callers.
- Tests reproduce the implementation instead of asserting behavior.
- Error paths are described but not exercised.
- The diff includes unrelated cleanup.
- Validation was skipped without a specific reason.
- You cannot explain why the change is safe.
For more direct evaluations of engineering practices, use PairFoundry’s Straight Answers hub.
Related questions people actually ask#
The adjacent questions all reduce to the same boundary: vibe coding becomes harmful when convenience replaces independent verification. The emotional framing—trap, failure, or shame—matters less than the operational question. Can the engineer state the invariant, explain the diff, and produce evidence that the repository still satisfies its contract?
Is vibe coding bad for you?#
It is bad for you when repeated delegation weakens your ability to inspect, reason about, and debug the resulting system. It is useful when it removes mechanical work while you retain architectural judgment. If you cannot continue without the agent after a failure, the workflow has consumed capability instead of extending it.
Why did vibe coding fail?#
It usually failed because the prompt specified the desired result but omitted a constraint the repository depended on. The agent optimized for the visible task, while the actual definition of correctness included callers, data assumptions, operational behavior, or compatibility requirements that were never supplied or tested.
Is vibe coding a trap?#
Yes, when fast code generation creates the illusion that implementation is the expensive part. In production work, proving that a change is correct and safe is often harder than producing it. The trap is accepting lower verification standards merely because the code appeared quickly and looked complete.
What is the biggest problem with vibe coding?#
The biggest problem is hidden, unverified assumptions. Syntax errors are cheap because tools expose them immediately. A mistaken transaction boundary, retry policy, authorization rule, or compatibility assumption can survive tests and review because the generated code looks coherent and no one explicitly challenged its premise.
Is vibe coding shameful?#
No. Using an AI coding agent is not shameful, and neither is exploring through generated code. Shipping changes you cannot explain or verify is an engineering failure, not a moral identity. Keep the tool, disclose uncertainty, enforce the repository’s invariants, and judge the work by evidence rather than authorship.