Claude Code writes most of our first drafts. Every change still goes through a plan approved by a developer, tests written before the code, review by agents that did not write it, a developer reading the diff, and CI. This article describes that setup as we run it today, which agents we use and for what, and what it caught on a recent Supabase project.

Project context comes first

Per-repository instructions. Each repository has a CLAUDE.md (or AGENTS.md) that Claude loads at the start of every session. It holds:

  • the stack and its pinned versions, for example "Bun 1.3, do not substitute npm";
  • the exact commands for format, lint, typecheck and tests, and which of them must pass before a task counts as done;
  • project conventions: code comment language, migration rules, branch policy;
  • links to deeper guides that load only when relevant, such as database conventions when touching migrations.

Shared rules. On top of the per-repository file, every session loads the same rule set:

  • testing: unit, integration and end-to-end tests, at least 80% coverage, tests written before the code;
  • security: no hardcoded secrets, input validated at every boundary, parameterized queries, authorization checked, error messages that leak nothing;
  • coding style: immutable data, small functions and files, explicit error handling, no silent failures;
  • git: conventional commit types, pull request descriptions built from the full branch diff with a test plan.

Memory. Claude keeps short notes between sessions on facts that are not in the code: where a service is deployed, which job builds it, which pitfalls already cost us time. The next session starts from those notes instead of rediscovering them.

Subagents we already run

A subagent is a separate Claude instance with its own context window, its own instructions, a limited set of tools and, where it matters, its own model. We use them for three things: planning that must not touch code, review by someone who did not write the code, and searches whose output would otherwise flood the main session.

AgentModelToolsWhat it does
PlannerOpusread-onlyTurns a request into an implementation plan: restated requirements, affected files, migrations, phases, testing strategy, risks. It can't edit code, and its plan waits for a developer's approval.
ArchitectOpusread-onlyReviews schema changes, access models and cross-module design, proposes a design and lists the trade-offs. Used when a change affects tenancy, roles or data ownership.
Database reviewerSonnetread, edit, shellReviews SQL and migrations: indexes on foreign keys and policy columns, correctness and cost of Row Level Security, grants, locking, migration safety, query plans. Can run diagnostics against the local database.
Security reviewerSonnetread, edit, shellGoes through the OWASP Top 10 for the change: authentication and authorization, injection, secrets, SSRF, unsafe crypto, input validation, error leakage. Runs after any code that touches user input, auth, APIs or sensitive data.
Language reviewersSonnetread, shellPer-language review, for example the Python reviewer: typing, idioms, concurrency, ruff and mypy. Runs the static analysers itself and reports by severity.
Explorefast modelread-onlyBroad searches such as "every place the frontend writes to table X" or "every RPC without a policy" that return a conclusion instead of file dumps.
Web fetchn/afetch onlyReads current documentation and changelogs, so library and API decisions rest on the actual version rather than on what the model remembers.

Planning and architecture run on the strongest model and can't write code. Reviewers run on a cheaper, faster model and get only the tools their job needs.

Commands wrap them: /plan runs the planner and stops, and no code is written until a developer confirms or edits the plan. /code-review reviews the uncommitted diff for security and quality and reports findings by severity. Language-specific commands such as /python-review run the matching reviewer.

How we brief reviewers. A reviewer gets the code and the scope, not the author's conclusions. If we hand it our own explanation of why the code is correct, it tends to agree with us, and the review is wasted.

Where we don't use subagents. Small edits, lookups where we already know the file, and steps that each depend on the previous result. Every handoff loses some context and costs tokens, so a subagent has to earn its place.

The flow for one feature

  1. Plan. /plan makes the planner list files, migrations, changes to access policies and grants, Edge Functions, UI screens, tests and risks. Changes to roles or tenancy also go to the architect. A developer approves, edits or rejects the plan.
  2. Branch. One feature per branch. Nothing is committed to the main branch directly.
  3. Tests first. For a Supabase module this means pgTAP tests for every policy and role (who can read, insert, update, delete, escalate, leave; anonymous access; cross-tenant access; schema checks such as "RLS is on for every table"), Deno unit tests for Edge Functions with I/O behind small interfaces, an end-to-end test against the local Supabase stack, and component tests for critical UI flows. The tests run and must fail before the implementation exists.
  4. Implement. Claude writes the smallest change that makes the tests pass. Migrations are append-only once merged. Database invariants such as atomic multi-row writes, "at least one owner" and single-use tokens live in SQL, not in the client.
  5. Verify locally. Format, lint, typecheck, supabase db lint, supabase db advisors, pgTAP, unit and end-to-end tests. Claude runs them and reports the actual output, failures included.
  6. Independent review. The database and security reviewers, plus a language reviewer where one applies, go through the diff in parallel. Each finding is fixed, or rejected with a written reason. Real issues found this way get a regression test.
  7. Developer review. A developer reads the full diff before it is committed. "The agent says it works" is not a merge criterion.
  8. Pull request. The description is built from the whole branch diff, with a test plan. CI runs the same checks as step 5 against a fresh local Supabase stack. A pull request merges only when CI is green and a person has approved it.

Guardrails

  • Permissions. Claude runs in a mode where read-only and routine commands go through, while destructive or outward-facing actions (force pushes, deletions, deploys, posting anywhere external) need explicit confirmation. Approval for one action doesn't carry over to the next.
  • Secrets. Production keys live in environment variables. They are not pasted into sessions, committed or printed in logs. Local development uses the Supabase demo keys only.
  • Honest reporting. Claude reports test results with their output and says when a step was skipped. Anything it could not verify is called out as unverified.
  • Pinned versions. Dependencies are pinned to exact versions. Before adding or upgrading a library, Claude checks the current release and changelog instead of relying on memory.

Tooling around Claude

  • Issue tracker and CI. Claude reads tickets, CI builds and logs directly, so a failing build is investigated from the actual log rather than a pasted fragment.
  • Browser automation. For UI work Claude drives a real browser: it reproduces bugs, checks flows after a change and reads console and network errors.
  • Text hygiene. User-facing text (pull request descriptions, docs, UI copy, messages to clients) gets a final cleanup pass before it is published.

What it caught on a Supabase project

We built the workspace, role and invite layer of a multi-tenant knowledge base SaaS on Supabase this way. Writing the pgTAP tests first exposed two problems before the code was finished:

  • Default grants on new tables left TRUNCATE open to the anon and authenticated roles, and TRUNCATE bypasses Row Level Security.
  • Inside a function with an empty search_path, comparing two citext values falls back to the case-sensitive text operator.

Then the security and database reviewer agents went through the finished code and found more. These five were the main ones:

  • email confirmation was switched off in the auth config, so someone who signed up with an invitee's address and got hold of the invite link could join the workspace with the invited role;
  • an invite kept working after the admin who sent it was removed from the workspace;
  • the sole owner of a workspace could not delete their account, which also blocked GDPR erasure requests;
  • an access policy forced a full table scan: an unfiltered article list on 600,000 test rows took 57 to 73 ms, and 0.44 ms after the fix;
  • three foreign keys had no index.

They also showed that some tests passed for the wrong reason: under Row Level Security a denied update changes no rows instead of raising an error, so a test that only checks the data afterwards proves nothing. The tests now count affected rows.

All five were fixed, and the four that changed behavior got regression tests. The smaller findings were fixed too, except one documented trade-off. Two of the new tests, one on the query plan and one on two owners leaving at the same moment, were also run against the broken version to confirm that they fail there. The project ended with 127 pgTAP checks, 36 tests for the Edge Function and 100% statement and branch coverage of the database functions and the Edge Function code, with CI failing below that line.

If you want this process on your project, our agentic AI engineers work this way, and we use the same review to take over apps built with AI coding tools.