Sertaç Yıldırımfield notes

Home → Engineering

Vibe Coding Rituals

"I finished it in three hours." Two days later we are debugging the same feature. The problem is not the tool — the tool is genuinely good. The problem is the absence of ritual.

Summary
  • Vibe coding is great for prototypes and dangerous in product code. Make the distinction up front.
  • The most common failure is "works but does not scale". Perfect with ten records, disastrous with ten thousand.
  • Frequent commits are your undo button. Commit at every working step.
  • Some places are written by hand: authorisation, money, deletion, data migration.

An honest distinction first

I am not saying do not use AI; I use it and so does the team. But there are two different kinds of work and they cannot be done the same way:

Vibe coding fits
  • Prototypes, validating an idea
  • One-off scripts, data transformation
  • A first step in an unfamiliar library
  • Test data, sample inputs
  • A demo that will be deleted tomorrow
Vibe coding does not fit
  • Product code that will live for years
  • Anywhere money is involved
  • Authentication and authorisation
  • Operations that delete or migrate data
  • Flows handling personal data

Teams that cannot tell the difference ship prototype-speed code to production and pay the bill three months later.

Three failures from the field

1. A query inside a loop

A report screen was generated and opened instantly in testing. In production it took 40 seconds. The code was this:

Works but does not scale
orders = OrderRepo.find(start, end);      // 8,400 records

for (o : orders) {
    o.customer = CustomerRepo.findById(o.customerId);  // ← 8,400 queries
    o.lines    = LineRepo.findByOrder(o.id);           // ← 8,400 more
}

The test dataset had 12 orders, so 25 queries — nothing to notice. Production ran 16,801. This class of bug is called N+1 and it is almost always born the same way: a solution that is correct for one record applied to a list.

Ritual: for every piece of generated data-access code, one question — "what happens if this loop runs ten thousand times?"

2. A library that does not exist

For a helper function, the suggested package had a perfectly plausible name and did not actually exist. npm install failed, it took two minutes to notice, no harm done.

The harmful version is different: suggesting a package that does exist but is abandoned, last updated four years ago. Install works, the code works, a security hole quietly walks in.

Ritual: when a new dependency is added, check three things — last release date, weekly downloads, open security advisories. It takes thirty seconds.

3. Tests that do not verify the code

The subtlest one. After the code was written we asked for tests. Tests were written, they all passed. Then someone noticed: the tests were verifying what the code did, not what it was supposed to do.

A useless test
// The code has a bug: the discount is applied as an amount, not a percentage
// And the test accepts the same bug as correct:

test("applies discount", () => {
  expect(applyDiscount(100, 20)).toBe(80);   // looks right
});

// But the code does: price - discount  (always subtracts an amount)
// Expected: price * (1 - discount/100)
// On 200 with 20% off: the code says 180, the correct answer is 160.
// Because the test used 100, both produce the same result.

Ritual: write the test first, or at least decide the expected values without looking at the code. A test written by reading the code verifies itself, not the code.

AI does not write wrong code; it does not know the answers to the questions you did not ask. The job is turning the right questions into rituals.

Ten rituals

1. Fix the target first

Before asking for code, write two or three sentences describing "done": what goes in, what comes out, which edge cases matter. Those sentences sharpen the prompt and make "is it finished?" answerable at the end. Best of all is writing them as a test.

2. The small-step rule

Do not accept 400 lines at once. Ask for one function, one file, one behaviour. The reason is not quality but verifiability: you can read and understand 40 lines, you will not read 400 — and code you did not read is code that was not reviewed.

3. Give the project rules in writing

Keep a rules file in the codebase and pass it as context every session: which libraries are used, how errors are handled, what the log format is, how layers are separated.

Example: project rules
# In this project
- Dates: java.time only. No Joda.
- Errors: our own ErrorCode enum, never throw raw exceptions.
- Data access: no SQL outside the Repository layer.
- Logs: structured logging, no string concatenation into messages.
- Tests: at least one unhappy-path test per service method.

Writing this takes half an hour and it is the best antidote to the consistency drift. It also helps every new joiner, so it pays twice.

4. Do not accept it without asking "why?"

Ask for a rationale for every unusual line in the generated code. If the explanation is not reasonable, neither is the code. This ritual does two things: it catches bugs early and it makes you learn — which is the real long-term win.

5. Commit at every working step

The most practical item on this list. With AI, code changes fast, and past a certain point going back to "it worked two steps ago" becomes impossible.

The rhythm
small step → tests green → commit
small step → tests green → commit

# When it breaks:
git diff HEAD~1          # what changed in the last step?
git reset --hard HEAD~1  # go back, try again

You can clean up with an interactive rebase at the end. It does not matter that the intermediate commits are ugly; they are your undo button.

6. The green bar rule

Do not move to the next feature while tests are red. It sounds obvious, but the natural flow of vibe coding encourages the opposite: to keep the feeling of speed you say "I will look at that later". Three "laters" in and you have lost track of what broke when.

7. The dangerous zone list

In some places a human writes the final version. Our list:

  • Authentication, authorisation, session handling
  • Money: pricing, discounts, tax, refunds
  • Operations that delete or migrate data (including migrations)
  • Flows handling personal data
  • Reconciliation with external systems

Taking suggestions is allowed; copy-paste is not. The criterion is simple: if it is wrong, can you undo it?

8. The three-attempt rule

If you are fixing the same error for the third time, stop. You are in a loop and the code is getting messier with each attempt. What to do: go back to the last working commit, read the error yourself, describe the problem, then start again.

This rule saved me the most time. Forty minutes in a loop costs more than ten minutes reading by hand.

9. Watch what you paste

Pasting logs while debugging is completely natural — and logs can contain customer emails, tokens, connection strings. Write the team rule down; without one, everyone invents their own in good faith.

10. Write it once yourself

If you are learning something new, read the generated code, close it and write the same thing yourself. You lose half an hour and the topic becomes yours. What I see in people who skip this: a year later they have shipped a lot and gone nowhere deep, and they freeze in the first real crisis.

What to look for in code review

Generated code has a smell. In review I look especially for:

SignalWhat it means
A query or HTTP call inside a loopFine on test data, fatal in production
A broad catch-everything blockErrors are being swallowed silently
A second helper class doing the same jobThe existing codebase was not consulted
Comments restating what the code saysUsually harmless, but a sign nothing was reviewed
A pattern or library not used in the projectConsistency drift beginning
Tests covering only the happy pathThe risky paths are untested

So does it actually make you faster?

Yes, but it depends where you measure. Ours looked roughly like this:

  • To a first working version: clearly faster. No argument there.
  • To a reviewed version: the gain shrinks, because review load rises.
  • To a version running cleanly in production: without rituals the gain can go negative.

So the speed is real; but what you should measure is not "how fast did I write it" but "how long until it was safely in production".

Checklist

Before opening a PR
  • Can I explain every line of this code?
  • Is there a query or external call inside a loop?
  • Do the tests verify the expected behaviour, or the current code?
  • If a new dependency was added, is it maintained?
  • Does it follow the existing patterns, or open a new path?
  • Did I touch a dangerous zone? If so, did I write it by hand?
  • Are there intermediate commits? (One giant commit makes rollback hard.)
  • Was there any secret data in what I pasted?

Conclusion

Vibe coding is not a skill but a mode. You need to know when to switch it on and when to switch it off. On in a prototype, off in a payment flow.

And back to "I finished it in three hours": that sentence was not wrong. What was missing was the rest of it — it was written in three hours, and nobody read it. All of these rituals are really trying to bring back one thing: reading.