- Book: Database Playbook
- Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
Hand a coding agent a failing test in a repo it has never seen, and watch where the first turns go. It reads package.json. It reads a README that still mentions a script someone deleted. It opens the CI file to see what really runs. Then it guesses.
The guess is where it goes wrong. If it types npx vitest in a shell that looks interactive, Vitest starts in watch mode and waits for file changes instead of exiting. Whether that happens depends on the harness, which is exactly the kind of guess you don't want it making. Running the whole suite to check one file costs every test in tokens and minutes, and which database reset is safe is left for it to work out alone.
Anthropic's own Claude Code best practices put the cost plainly: "Claude stops when the work looks done. Without a check it can run, 'looks done' is the only signal available." The same page tells you to put "Bash commands Claude can't guess" in your instructions file.
A Makefile is the cheapest way to make those commands hard to guess wrong: one stable name per job and a help target that lists them all. Ten targets do it.
The Makefile
This one is for a Node and TypeScript service with Postgres through Prisma. Swap the recipes for your stack. The target names are the part worth keeping.
.DEFAULT_GOAL := help .PHONY: help setup test test-one lint fmt typecheck \ dev db-reset check PORT ?= 3000 help: ## List every target @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ | awk 'BEGIN {FS = ":.*?## "}; {printf "%-10s %s\n", $$1, $$2}' setup: ## Install deps exactly as locked npm ci test: ## Run the whole suite once, no watch mode npx vitest run test-one: ## One file: make test-one FILE=src/a.test.ts @test -n "$(FILE)" || { echo "need FILE=path"; exit 2; } npx vitest run $(FILE) lint: ## Lint without auto-fixing npx eslint . fmt: ## Rewrite files to the house format npx prettier --write . typecheck: ## Types only, writes no files npx tsc --noEmit dev: ## Dev server: make dev PORT=4000 PORT=$(PORT) npm run dev db-reset: ## LOCAL only: drop, migrate, seed @case "$$DATABASE_URL" in \ *@localhost:*|*@localhost/*) ;; \ *@127.0.0.1:*|*@127.0.0.1/*) ;; \ *) echo "db-reset: DATABASE_URL is not local"; exit 1;; \ esac npx prisma migrate reset --force npx prisma db seed check: lint typecheck test ## The done check npx prettier --check . Recipe lines must start with a real tab (GNU make manual). If a copy-paste turns them into spaces, make rejects the file, so check that first when it complains. The help recipe is the self-documenting pattern marmelab published in 2016: any target with a ## comment on its line shows up in the list.

One switch per job. The agent never has to rewire anything to run the tests.
What each target is for
-
make help, the default goal..DEFAULT_GOAL := helpmakes plainmakeprint the list, so an agent's first exploratory command returns the whole menu instead of building something. -
make setupinstalls dependencies from the lockfile withnpm ci, notnpm install, so a fresh worktree gets the versions you tested against. This is also the one command a new clone or a new worktree needs before anything else. -
make testruns the suite once withvitest run. Spelling outrunmatters: per the Vitest CLI docs, plainvitestis watch mode in a development terminal and only falls back to a single run in CI or when stdin is not a TTY. -
make test-one FILE=...runs one file. Claude Code's own best practices suggest "Prefer running single tests, and not the whole test suite, for performance." Thetest -nline fails fast with a usage hint instead of silently running everything when the agent forgetsFILE=. Vitest treats the argument as a substring filter on the path, so a partial name works too. -
make lintlints without--fix. You want the agent to see the findings and fix them in a turn you can review, not have a tool rewrite twenty files behind its back. -
make fmtis the opposite on purpose:prettier --writerewrites files in place. Formatting is never worth a debate, so give the agent one command that ends it. -
make typecheckrunstsc --noEmit. Types only, no build output to clean up, and a non-zero exit when something is wrong. -
make dev PORT=4000starts the dev server.PORT ?= 3000assigns only ifPORTis not already defined (conditional assignment), and make turns every environment variable it sees into a make variable (Environment). SoPORT=4000 make devandmake dev PORT=4000both work, and two agents in two worktrees can run side by side on different ports, as long as your server readsPORT. This target never exits, so tell agents in AGENTS.md whether they may start it at all. -
make db-resetis the only target here that destroys data. Thecaseguard is a rough check: it refuses to run unlessDATABASE_URLhas@localhostor@127.0.0.1followed by a port or a slash, and it assumes the URL is exported in the environment. It is a speed bump that fails closed, so it also rejects local URLs written another way, such as a socket path; adjust the pattern for your setup. Prisma's reset docs also describe a guard of their own: when an agent such as Claude Code or Cursor attempts the reset, Prisma blocks it and the agent has to ask for your explicit consent. I haven't checked whether that detection fires when the command runs inside make, so treat the URL check as yours. Note that since Prisma 7,migrate resetno longer seeds on its own, which is whydb seedis a separate line. -
make checkis the gate. Lint, types, tests, then aprettier --checkthat exits 1 if anything is unformatted.
Two lines in AGENTS.md
The Makefile only helps if every agent knows to use it. AGENTS.md is the shared place for that: the site lists Codex, Cursor, Gemini CLI, Aider, goose, opencode, GitHub Copilot's coding agent and others as supporting it, and it suggests putting build and test commands there. Two lines are enough:
Run `make help` for every task. Use make targets, not raw commands. A task is finished only when `make check` passes. Claude Code reads AGENTS.md directly from v2.1.277, but by default only when there is no CLAUDE.md or CLAUDE.local.md in your working directory or above it (memory docs). If you already have a CLAUDE.md, add one line to it so both files say the same thing:
@AGENTS.md That @path import expands the file into Claude's context, and the same docs say keeping the import never makes Claude read AGENTS.md twice.

One file, every agent. The targets are the contract; AGENTS.md is the signpost.
make check decides when the task is done
make check lists lint typecheck test as prerequisites, so they run first and the formatting check runs last. When any recipe exits non-zero, make "gives up on the current rule, and perhaps on all rules" and exits non-zero itself (GNU make, Errors). That single exit code is the pass or fail signal the Claude Code best practices ask for: "Give Claude something that produces a pass or fail, and the loop closes on its own."
Two choices to make on purpose. First, make -k check keeps working through the other prerequisites after one fails (-k), so lint, types and tests all report in one run, which suits an agent fixing several things in one turn. Second, keep check free of anything that writes: no --fix, no --write. A done check that edits files can pass on code the agent never saw.
You can make the gate harder than an instruction. The same docs describe a Stop hook that "runs your check as a script and blocks the turn from ending until it passes." With the Makefile in place, that script is one line: make check.
Running it per task in Hermes IDE
I build Hermes IDE, so weigh this section with that in mind. Hermes runs Claude Code, Codex and other CLI agents each in its own terminal, and can start every task on its own branch and worktree. A file called .hermes/worktree.toml tells it how to prepare each new worktree, per the worktree recipe docs. With the Makefile above, the recipe is three lines:
# .hermes/worktree.toml setup = ["make setup"] copy = [".env"] done_when = ["make check"] setup runs in order inside the new worktree, and the first failing command stops the rest. copy brings over files git ignores, like .env, and refuses the whole recipe if a pattern matches a tracked file. done_when sets the checks Hermes runs to decide a task is done, here make check. The first time Hermes sees the recipe, and whenever it changes, it shows you the commands and runs nothing until you choose Run setup.

The ⌘N New task sheet: Claude Code on orders-api in a new hermes/ worktree, with Accept edits, model and effort chips and a preview of the command Hermes will run.
Hermes, a CI job, a Stop hook and you at the terminal all run the same make check.
If this was useful
Of the ten targets, db-reset is the one that can hurt you, and it gets harder to write safely the more your data store does: migrations, seeds, extensions, a second store for cache or search. Database Playbook is my short guide to choosing the right store for each system and the trade-offs you live with after. If agents are about to touch your schema, it is a good read before they do. And if you run several agents at once, Hermes IDE is free, source-available under BSL 1.1, on macOS, Windows and Linux (GitHub).


