The fastest way to make Claude Code feel like a useful teammate is to forbid it from doing the things a new teammate would do badly. New teammates love to invent. They write a fresh utility instead of finding the existing one. They mock an API instead of asking which endpoint to call. They add a new endpoint instead of reusing the one already shipped. Each of these is a tiny act of “I will just do it myself” that costs you a code review and a refactor.

This rule kills all three behaviors. It is short. It is mostly negative (“never do X”). It is the single most-cited rule in my own code reviews after I added it.

The story behind the rule

The triggering moment was a feature where the model decided to use mock data because “the real API was not available yet.” It was. The endpoint already existed, the spec was committed, the SDK had a typed client. The model did not search for it. It generated a fake array of three users with Math.random IDs, wired it into a hook, and proudly delivered the feature. I noticed the mock during review. The fix was a thirty-minute rewrite that should have been the first version.

The second incident was the model casually modifying a function called getApiBase() to add a special case for a feature it was building. The function was used by twenty other components. The “special case” was a if (path.startsWith('/v2/foo')) branch that returned a different host. Three other features broke silently because their requests to /v2/foo/* were now going to the wrong host. This was caught by an integration test, not by me, which is the only reason I learned the lesson cheaply.

The third was the model writing formatDate(d) as a helper inside a component, except formatDate already existed in src/utils/formatDate.ts and the version it wrote handled timezones differently. Two formatDate functions in the same codebase is a worse problem than no formatDate function.

All three are “the model is being helpful in a way that ignores prior art.” The rule is the antidote.

The rule

The complete rule from ~/.claude/rules/development.md:

# Development Rules

- NEVER create or use mock data while developing.
- NEVER modify user-defined global API endpoints. Create a new
  one if unsure, and ask the user to confirm.
- All API endpoints and related hooks already exist in the
  project; reuse them whenever possible and ask if you can't
  find one.

## Helper Functions

- Check for existing helper files before creating new utilities.
- Common locations: `src/utils/`, `src/helpers/`, or
  feature-specific helper files.
- If creating a new helper, consider if it should be shared or
  feature-specific.

Like the code-style rule, this one is short on purpose. The three negative rules cover the three failure patterns I keep seeing. The positive guidance (“check existing helpers, ask before creating”) is just the inverse of the negative rules.

Why no mocks

Mocks are a debugging tool, not a development tool. They have specific use cases:

  • A test fixture that exercises a code path that depends on an external service.
  • A local development setup where the real backend is not running.
  • A demo of a feature whose backend is genuinely not yet built.

In all three cases, the human writes the mock with intent. The mock has a clear lifetime (“until the real endpoint ships”), a clear scope (“only in this test”), and a clear owner.

What the model produces when left to invent mocks is none of those things. It is a hardcoded array of three records, with id: 1, name: 'John Doe', indistinguishable from real data, with no comment explaining when to remove it. The mock leaks into a PR, gets shipped, and then production data and mock data coexist in the same code path until someone notices that the dashboard always shows three users.

The rule says no, because the cost of asking “where is the real endpoint?” is one question. The cost of cleaning up a mock that escaped review is hours.

Why no modifying global API endpoints

Anything named like getApiBase(), apiClient, BASE_URL, ENDPOINT_MAP, or a similar global is load-bearing. It is used by every feature that talks to the backend. Modifying it for a single feature’s special case is a fan-out problem: one feature gets the change it needs, twenty other features get a behavior change they did not ask for.

The rule’s instruction is “create a new one if unsure, and ask the user to confirm.” That is the right shape. If the model genuinely needs a different endpoint for this feature, the safe move is to create a new constant, a new client, a new function, leaving the global untouched. Then ask the user “is this the right approach, or should the global be modified?” If the user says modify, the user is taking responsibility. If the user says no, the new local thing is the right answer.

The “ask the user” part matters. Without it, the model creates a new endpoint and silently picks a name. With it, the user names the new endpoint or rejects the approach entirely. Either way, the global stays clean.

Why reuse helpers

This is the same logic as the API rule, scoped down to utilities. formatDate, validateEmail, parseQuery, slugify, debounce, chunk: every codebase has its own version of these. The codebase’s version handles its own edge cases, its own timezone conventions, its own validation patterns. If the model writes a fresh one, you now have two implementations that drift over time.

The rule says check first. The “common locations” list (src/utils/, src/helpers/) is what I use in my own projects; other projects might use lib/, shared/, common/. The exact path is less important than the habit of searching before writing.

The escalation path is “if creating a new helper, consider if it should be shared or feature-specific.” This is the one place where the model gets to make a small choice: a helper that only this feature uses can live next to the feature; a helper that other features might want goes into the shared folder. Both answers are fine, what matters is that the model considers the question.

When the rule does not apply

The rule has obvious exceptions, none of which I needed to write down:

  • Greenfield projects where there are no global endpoints yet. If the project has no apiClient, the rule about not modifying it is vacuously satisfied. The first version of the global is whatever the model writes, and you review it.
  • Spike branches where mocks are the point. A research spike that asks “what would this look like if the API returned X?” needs mocks. The rule says no mocks “while developing.” Spikes are an explicit exception you state in the conversation.
  • Test files. Mocks are required in tests. The rule scopes to production code paths. The model usually understands this without being told, but if it gets confused, scope the rule yourself (“no mocks outside test files”).

For the helper rule, there is no exception. Even in a greenfield project, the model should check before writing a new helper. The check is cheap.

Variants you might want

  • Stronger “ask before creating” rules. Some teams want the model to ask before creating any new file. I find that too noisy; the rule above (“ask if you can’t find an existing endpoint”) is the right granularity for me.
  • Per-feature allow lists for new APIs. If your project has a strict gateway pattern (every backend call goes through one module), state that in the rule. The model will follow.
  • Explicit linter integration. A natural extension is “before suggesting a new utility, run grep -r 'formatDate' src/.” I do not formalize this in the rule, but I do ask the model to grep when I am reviewing its work, which is the same outcome.
  • Mock-allowed mode. For a personal sandbox project, I sometimes flip the rule with “mocks are fine in this directory.” I do this by adding an .claude/CLAUDE.md in that subdirectory that explicitly relaxes the global rule. The override is local, the global stays strict.

Closing note

The thing I find most useful about this rule is not the negative restrictions, it is what it changes about the model’s behavior at the start of a task. Without the rule, the model dives in and writes. With the rule, the model first opens src/utils/, scans for existing helpers, opens the API client, checks for the endpoint. The first action of a task becomes “look around,” not “start writing.”

That tiny shift in first action is most of what makes a teammate feel competent. Competent teammates look first. Beginners write first. The rule makes the model behave like the competent version.

For related discipline on commits and branches, see the safety rule on no-auto-commit in this same toolkit series. The two rules together produce a model that searches before it writes and asks before it ships.