Discovery first
The fastest way to spot code written by someone who joined yesterday is to read its error handling. The logic is fine. The naming is sensible. And it does not look like a single other file in the repo. Its own little dialect: a response wrapper nobody else uses, a custom error type where everything else returns the shared one, validation inlined where the rest of the codebase has a guard layer. In isolation it passes review. Next to its neighbors it reads like a guest who brought their own plates.
I have shipped that code. So I have a rule now, and it is boring on purpose: no code until I have searched for how the thing is already done. Search, report what I found, get a nod, then build. A hard wall between understand and build, with nothing leaking across it early.
The order matters more than it sounds. When you write first and reconcile later, the reconciliation never really happens. You have a working diff, the tests are green, and now matching the house style is a chore standing between you and merge. So you skip it, or you do half of it, and the dialect ships. Discovery first flips the cost. You pay the lookup before you are attached to anything, when changing course is free because there is no course yet.
Here is the shape, concretely.
Discover
Grep the file you are about to touch. Then grep its siblings. You are not reading for understanding in general, you are hunting for the specific pattern you are about to need: how do handlers in this directory return success, how do they return errors, where does validation live, what gets logged.
Cite what you find. Actual paths, actual lines.
handlers/users.go:42 success: c.JSON(200, Envelope{Data: u})
handlers/users.go:55 error: return apiErr(c, 400, "invalid_email")
handlers/orders.go:31 same envelope, same apiErr helper
handlers/teams.go:28 same againFour files, one pattern, said three times. That is not a suggestion anymore. That is the contract.
Checkpoint
Stop. Before a single line of the new handler exists, say three things out loud. Here is the existing pattern. Here is my plan, which matches it. Here is the one place I deviate, and why.
Pattern: handlers return Envelope{Data: ...} on success, apiErr(c, code, "msg") on error. Seen in 4 files.
Plan: new handler uses the same Envelope and the same apiErr, shaped like its neighbors.
Deviation: one extra 409 for the duplicate-name case, which none of the four handle. On purpose.The deviation line is the whole point. Matching blindly is not the goal. Sometimes the existing pattern is wrong, or your case genuinely differs, and then you deviate on purpose and you say so. The difference between a deliberate deviation and an invented one is whether anybody knew it was happening. One is a decision. The other is a surprise somebody finds in three months.
Then you wait for the nod. This is the hard stop, and it is hard because skipping it feels productive. You already know what to write. Waiting feels like stalling. It is not. It is the cheapest moment to be told you are about to do the wrong thing.
Implement
Now you build, and building is almost dull, because the interesting decisions already happened. You copy the envelope. You call the same apiErr helper. Your handler is shaped like its four neighbors, plus the one documented place it is not.
The new endpoint reads like it was always there.
Why the cold version goes wrong
Write a handler with no lookup and you do not produce nothing. You produce something plausible. You invent a response envelope because every API has one and yours seems reasonable. You invent an error shape because errors need a shape. None of it is bad in the way a bug is bad. It is bad in the way a foreign accent is bad in a codebase: it works, and it costs every future reader a double take, and it quietly licenses the next person to invent their own thing too. Patterns rot one reasonable exception at a time.
Discovery tethers the work to what exists. It does not make you smarter. It just stops you from inventing in a corner where the answer was already sitting in the next file.
The agent case
This is old advice for humans joining a codebase. Read before you write. It just used to be cheap to ignore, because a human only writes so fast, and a human feels the friction of a foreign pattern, the nagging sense of "surely there is already a way to do this here."
An AI agent does not feel that. It will generate a clean, confident, internally consistent error type that exists nowhere else in your repo, in seconds, and it will do it in five files before you have finished reading the first. The hallucination is not a syntax error you can catch. It is a plausible-but-foreign pattern, produced faster than anyone can notice, and plausible is exactly the kind of wrong that slides through review.
So the discovery step stops being a nicety and becomes a control. Make the search and the checkpoint a required phase, not a habit you hope holds. Force the agent to grep first, cite what it found, state where it is deviating, and only then write. Same rule that was always good manners for a new hire. The agent is just the version of the new hire that types faster than you can object.
Search first. Build second. Never the other way.