Vibecoding · · 7 min read
5 vibecoding anti-patterns that wreck a project
No spec, unread code, zero tests, context rot and mega-prompts. Five mistakes and how to avoid them.
Vibecoding does not wreck a project on its own. Your habits do — the ones you carried over from the days when you wrote code slowly and by hand. When a model generates a hundred lines in three seconds, every bad habit starts costing ten times more, because it scales with the speed. Below are five anti-patterns you see in every project rescued after two months of “shipping fast”. For each one: why it hurts, how you know you have fallen into it, and what to do about it.
Anti-pattern 1: no spec, vague prompts
The most common mistake. You type “build me an admin panel” and hit enter. The model guesses. It guesses the tables, guesses the roles, guesses whether filtering happens on the server or the client. Some guesses land well, some land terribly — and you have no baseline against which to say it guessed wrong.
Why it hurts: without a spec, “correct output” does not exist. Anything the model returns is equally acceptable, so you settle for the first version that compiles. A week later you have ten screens, each interpreting “user” differently.
Symptom: lots of “no, not like that, fix it” rounds. If you run a five- or six-turn dialogue with the agent to arrive at what you had in your head from the start, the spec was living in your head, not in the prompt.
Fix: before asking for code, write a short description — three to five sentences of inputs, outputs and constraints. It need not be a formal document; a list is enough: what the function takes, what it returns, what is out of scope, what the edge cases are. For larger changes (more than a few files) write a real SPEC.md and ask the agent to first summarize how it understood the task — before it writes a single line.
Anti-pattern 2: accepting generated code without reading it
The diff is 200 lines, it looks reasonable, the tests are green, so you click “accept all”. That is the moment you stop being an engineer and become a conveyor belt between the model and the repository.
Why it hurts: models generate code that looks like it works. Subtle things — an inverted condition, a missed empty-list case, a catch that swallows the error — pass the “looks OK” eyeball test. Worse: a model will happily add a dependency you do not need, or duplicate logic that already exists three folders over. If you are not reading, the debt piles up quietly.
Symptom: you cannot explain why a given function works. When a colleague asks “why is there a 250 ms setTimeout here?”, you answer “the model put it there”. That is a red flag — code you do not understand is code you cannot maintain.
Fix: read every line you approve as if you were reviewing a colleague. If a diff is too big to read carefully, it is too big for one commit — ask for smaller steps. Ask the agent “why this and not that” on any fragment that surprises you. Treat generated code like a PR from a very fast but very overconfident junior.
Anti-pattern 3: skipping tests because “it works”
You click around the app, everything runs, so why waste time on tests. Vibecoding actively tempts you here: since code appears so fast, tests feel like a brake. It is a trap that snaps shut exactly when the project starts to have value.
Why it hurts: “it works” means “it worked once, on my machine, on the path I happened to click”. Without tests, every further model-generated change is a gamble — you do not know whether it broke something three modules away. And with vibecoding the changes are many and fast, so the regression catches up with you in no time.
Symptom: you are afraid to make changes. Every refactor is roulette, because the only verification is manual clicking. Bugs that were “already fixed” keep coming back. That is the classic missing safety net.
Fix: write tests for what actually hurts when it breaks — business logic, validation, calculations, error paths. Do not chase 100% coverage; chase risk coverage. Models are great at generating tests, so use that: ask for tests before the implementation or alongside it, and check yourself that they test behavior, not implementation. One good test on a critical path is worth ten on a getter.
Anti-pattern 4: context rot — dumping the whole repo into the prompt
“So the model has full context”, you paste fifty files or the entire repository into it. Intuition says: the more context, the better. Here, intuition is wrong.
Why it hurts: a model’s attention is a finite resource. The more irrelevant files you dump in, the more the signal blurs. The model starts mixing conventions from different modules, reaching for patterns from whatever code happened to be in the window rather than the code that matters. This is called context rot: answer quality drops as noise rises, even when the window formally fits.
Symptom: the longer the session, the worse the answers. The model starts to “forget” decisions from ten messages ago, mixes up names, proposes solutions inconsistent with the rest of the project. You paste more and more, and the results get weaker.
Fix: give the model only what is relevant to the task — two or three files plus a short architecture note, not the whole dependency graph. Use files likeCLAUDE.md where you write conventions down once instead of pasting them into every prompt. Start a new session for a new task — a fresh, clean context beats a long, cluttered conversation. Less, but sharper.
Anti-pattern 5: mega-prompts that ask for everything at once
“Build me OAuth auth, a user panel, a payment system, email notifications and an analytics dashboard” — in one prompt. The model will produce something, because it always produces something. The only question is how much of it is usable.
Why it hurts: the larger the scope of one answer, the more decisions the model makes without you and the less attention it gives to each. You get five half-working features instead of one good one. Debugging becomes a nightmare, because when something is off you do not know whether the problem is in payments, in auth, or in the way the model wired one to the other. Everything is half-done at the same time.
Symptom: you get a giant wall of code you can neither verify nor safely merge. Your first reaction is “OK, but where do I even start checking this”. If the answer to one prompt does not fit into one sensible review, the prompt was too big.
Fix: break the task into the smallest sensible steps and ship them one at a time. Auth first — read it, test it, merge it. Then the panel. Then payments. Each step ends with a working, verified piece that the next one stands on. It looks slower and is actually faster, because you are not constantly going back to untangle a knot. Small prompts, small diffs, small risk.
Why these five travel together
These anti-patterns reinforce each other. No spec (1) means you do not know what to look for when reading a diff (2). Since you are not reading anyway, why bother with tests (3). To let the model “figure it out itself”, you dump everything into it (4) and ask for everything at once (5). Each one alone is survivable; together they make a project that works in the demo and falls apart on the first real user.
They share one common denominator: you are handing the model decisions that should stay with you. Vibecoding is not “the model does it for me”, it is “the model writes, I decide”. When that line blurs, speed turns into debt.
TL;DR
Five anti-patterns that wreck a project: (1) no spec, vague prompts — write a short description of inputs, outputs and constraints before asking for code; (2) accepting code without reading it — read every line as if reviewing; (3) skipping tests because “it works” — test risk, not percentages; (4) context rot — give only the relevant files and start fresh sessions; (5) mega-prompts — break into small, verifiable steps. The shared cure: keep the decisions with yourself, hand the model only the typing.