Skip to content

Code Audit Team

Every change in a GAIA project is checked twice before it merges. The Quality Gate runs before each commit, and the Code Audit Team reviews the pull request before it merges.

The audit is not one reviewer but a team. Each reviewer on the team (a member) is a Claude sub-agent responsible for a set of your files, and a pull request is reviewed by every member whose files it changes. The audit runs locally in your Claude Code session. Nothing runs Claude on GitHub Actions (opens in a new tab) (why).

A hook enforces it: Claude cannot merge until every member whose files changed has approved the change, so it runs the audit first. The GAIA-Audit required check covers the one way around that hook, the merge button on github.com: a pull request merged there waits until Claude has run the audit.

The Quality Gate is the full set of checks Claude runs before it commits a change to source code. The pre-commit hook checks only the files you staged. The gate checks the whole frontend/ package, so it also catches what a change breaks somewhere else.

Before any commit that stages source code (.ts, .tsx, .js, .jsx, .mjs, .cjs, or .css) or a config that changes what the checks do: package.json, the lockfile, the TypeScript (opens in a new tab), Vite (opens in a new tab), Vitest (opens in a new tab), Playwright (opens in a new tab), and ESLint (opens in a new tab) configs, and GAIA’s package registry. A commit of only Markdown, .claude/ files, wiki pages, or images skips the gate.

StepPasses when
SimplifyClaude has run the simplify skill on the change and applied the changes it recommends.
LocalizationNo user-facing text is hardcoded, and no translation key is missing its value.
Typecheck (pnpm typecheck)There are zero errors.
Lint (pnpm lint)There are zero errors and zero warnings. Lint also fixes formatting, because Prettier (opens in a new tab) runs as an ESLint rule.
Unit tests (pnpm test --run)Every test passes with zero console warnings. Stories and hook tests run in headless Chromium.
End-to-end tests (pnpm pw)Every Playwright test passes.
Dev serverThis checkout’s own dev server starts and its home page loads. Claude stops only the server it started, so it never stops a server another session is using.
Build (pnpm build)The production build succeeds.

Claude fixes every warning, not only errors, before it reports. It then shows you a table of the results and asks whether to commit or hold the change for your review. Inside a workflow that commits on its own (the audit’s fix round, /gaia-debt, and a plan’s phase commits), a clean gate commits without asking, because that workflow has its own point where you review.

The gate is a rule Claude follows, not a hook. The pre-commit hook is the part that runs no matter what (gate reference). The shipped gate checks only frontend/. A package you add beside it, such as a backend, needs a gate of its own (The backend is left to you).

The list of members (the roster) lives in .gaia/audit-ci.yml under auditors:. Each entry assigns a set of file patterns (globs) to a member. You change the roster by asking Claude, not by hand (Covering new code). Two members ship:

MemberOwnsReviews for
code-audit-frontend (the default member)The app and its tooling: frontend/app/**, frontend/test/**, frontend/.storybook/**, frontend/.playwright/**, the package manifests and lockfile, build and lint configs, frontend/.claude/**, frontend/CLAUDE.md, and root tooling such as .npmrc and .nvmrcSecurity, performance, fit with the existing architecture, edge cases, accessibility, and maintainability: problems that take understanding what the code is meant to do and how data moves through it, beyond what lint and types catch. Library-specific rules for shadcn/ui (opens in a new tab), cn, Conform (opens in a new tab), the form components, React patterns, and react-i18next load from frontend/.claude/agents/code-audit-frontend/.
code-audit-github-workflows.github/workflows/*.yml and .github/actions/**/*.ymlWorkflow YAML problems: unsafe third-party actions, script injection, too-broad permissions, and mishandled secrets.

Each file goes to the first member whose pattern matches it, and the default member’s patterns are checked last, so a workflow file belongs to code-audit-github-workflows.

code-audit-frontend also runs three tools and lists what they find in its report, as advice that never blocks approval:

  • React Doctor (opens in a new tab) for React problems, when the change touches a .ts or .tsx file.
  • Knip (opens in a new tab) for unused files, exports, and dependencies, when the change touches a .ts or .tsx file, a package.json, pnpm-workspace.yaml, or the lockfile.
  • A check for known security advisories in your dependencies, on every review.

If GAIA cannot work out what the change touches, all three run.

Members only report problems. They never edit a file. A separate fixer makes the repairs, and a script checks each repair before it is kept.

.gaia/audit-ci.yml ships with GAIA. Ask Claude to change it when your project grows. When you add code beside frontend/, such as a backend, decide who reviews it:

  • Give it its own member. A member is a Claude Code sub-agent (opens in a new tab) file in .claude/agents/ that says what to review for, plus an entry under auditors: that gives the member’s name and file patterns. This is the better choice for code in another language or stack.
  • Add its patterns to an existing member whose review fits the code.

Until you do one of these, the new files are not skipped. A file no member covers goes to code-audit-frontend, the default member, so the merge still waits for a review, but one written for a React app. Only a pull request whose every file is outside the audit’s scope (wiki pages, files in the root .claude/ folder, GAIA’s own .gaia/ files, docs/, and Markdown files at the root; frontend/.claude/ is in scope) merges with no member’s review.

Claude follows the steps on your project’s wiki page wiki/concepts/Registering a Code Audit Team Member.md. To add a member, give Claude a prompt like this one, with your own folder and stack:

Add a Code Audit Team member for the Python API in backend/. Follow the wiki page "Registering a Code Audit Team Member": start from a copy of the workflows member, name it code-audit-python, write its review guidance for Python and FastAPI, add its roster entry in .gaia/audit-ci.yml covering backend/**, register any finding classes it needs, then run the roster check and repair everything it reports.

What each step is for:

  • Start from a copy of the workflows member. code-audit-github-workflows is the smallest member that ships, and its file already has every section a member needs. Claude rewrites what it reviews for and keeps the rest.
  • The name starts with code-audit-. The audit loop’s round limit applies only to reviewers named that way, and the name is also what registers the agent file as part of the audit machinery (Per-member clearance). With that name, a later edit to the member’s own instructions sends every member the pull request involves back to review, instead of keeping their earlier approvals. The roster check reports a member without the prefix.
  • The agent file carries a copy of the member’s file patterns. The roster decides which files a member is sent, and the member reads its own copy to know which of the changed files are its to review. GAIA generates that copy from the roster, and the roster check reports when the two disagree.
  • Finding classes. A member that reports kinds of problem GAIA’s lists do not have registers them in .gaia/finding-classes.json, so /gaia-codify can count them.

The roster check compares the roster with the files in your repository, and also checks that the light review settings in .gaia/audit-ci.yml are valid. The pre-commit hook runs it whenever a commit stages .gaia/audit-ci.yml, a member’s agent file, or the audit machinery list, so a broken roster cannot be committed; Claude repairs what it reports and commits again. It reports:

  • A tracked file that no member covers and that is not on the unowned: list.
  • A pattern, in the roster or in the unowned: list, that matches no file.
  • A member whose copy of its file patterns no longer matches the roster. Claude regenerates the copy and runs the check again.
  • A member whose name does not start with code-audit-, or whose agent file is not registered as audit machinery.

The unowned: list in .gaia/audit-ci.yml holds paths you have decided no member needs to review, such as root Markdown files or images. Only the roster check reads it, to tell a deliberate gap from a forgotten one. Listing a path there does not skip review. A file no member covers still goes to code-audit-frontend.

GAIA’s own folders, .claude/, .gaia/, .githooks/, and .github/audit/, need no entry: the roster check does not report a file there that no member covers, including one you added yourself, such as your own hook or rule. To have such a file reviewed, add its pattern to one of your members.

To check the roster on its own, ask:

Run the Code Audit Team roster check, repair any member whose file patterns no longer match the roster, and explain anything left in plain language.

If .gaia/audit-ci.yml has no auditors: block, the audit stops rather than fall back to a built-in list, and the message tells Claude to restore the block from the copy GAIA ships. If you had added members of your own, check that they came back, or ask:

.gaia/audit-ci.yml has lost its auditors: block. Restore it from the GAIA release this project is on, keeping any members I added.

code-audit-frontend keeps the rules for specific libraries in frontend/.claude/agents/code-audit-frontend/, one Markdown file per library. GAIA ships files for cn, Conform (opens in a new tab), the form components, React buckets, react-i18next, and shadcn/ui (opens in a new tab). When you add, replace, or remove a library whose usage is worth checking in review, add, replace, or delete its file. The agent file itself does not change.

Each file says which rule specialists read it: react-patterns (.tsx files), typescript (.ts and .tsx files), or translation (language files and files that call the translation functions). It says so in a subagents: line in the header block at the top of the file (its frontmatter), written as a list on one line, such as subagents: [react-patterns, typescript]. A file whose list spans several lines, or that has no subagents: line, is reported as an error finding in the next review. The rules themselves are plain Markdown. To add a file, give Claude a prompt like this one, with your own library:

Add a code-audit-frontend library rule file for TanStack Query in frontend/.claude/agents/code-audit-frontend/. Include the usage mistakes a reviewer should flag, with a bad and a good example for each, and send its rules to the react-patterns and typescript specialists.

Each member’s approval (its clearance) covers exactly what the branch changed in that member’s files, plus the files that decide how the audit runs, since those decide what each member sees. That second set (the audit machinery list) covers the roster (.gaia/audit-ci.yml), the members’ and reviewers’ agent files, the audit’s scripts and hook libraries, everything under .github/audit/, the root rules in .claude/rules/, and .gaia/VERSION. A machinery change sends back only the members a pull request already involves: when a pull request also changes a member’s files, every member it involves reviews again, the machinery change included, but a pull request that changes only root rules or other files outside the audit’s scope merges with no member’s review. A GAIA update that also changes app files falls in the first case. You will notice three effects:

  • Members run in parallel, in any order.
  • A commit outside a member’s files and the machinery list never sends it back to review. A docs change or a wiki page triggers no new review.
  • Merging the base branch into yours does not trigger a new review, unless the merge commit contains its own edit, or a change from the base branch lands within three lines of your branch’s changes.

A member sent back to review looks only at what changed since its last approval or refusal, plus any of its findings still open.

A small change since a member’s last full review can go to a cheaper Sonnet reviewer. code-audit-frontend has this on by default. Some changes always get a full review, whatever their size: build and lint configs, package.json and the lockfile, tests and stories, CI workflows, and the Claude setup files in your repository (CLAUDE.md and .claude/). Whenever GAIA cannot confirm a change qualifies, the full member runs. The settings are in Agents.

The same reviewer can also take a first review. When a small frontend change is purely presentational (copy edits, swapping one HTML tag for another, static class changes, translation text with the same keys), a script checks the shape of the change and sends it to the quick reviewer instead of the full code-audit-frontend review. The quick reviewer answers one question: does anything here need to block the merge? It checks accessibility, translations, and theme color names (design tokens). When the answer is yes, or it is not sure, the full member reviews the change.

Every finding is graded on one scale:

SeverityEffect on approval
CriticalMust be fixed.
MajorMust be fixed, or acknowledged with a stated reason.
MinorNever blocks approval. Fixed by default.

A fourth grade, severity:investigate, marks a tech-debt issue whose severity cannot be judged yet. See Investigate issues.

Every finding, Minor included, gets an outcome. By default it is fixed. Otherwise it is accepted as a known trade-off (a residual), waived, or filed as an issue, each with a reason. Findings about architecture come to you to decide.

Out-of-scope findings (problems the audit notices outside the pull request’s own change) are filed as tech-debt issues through the file-tech-debt skill. A non-security finding in a file the pull request already changes can instead be recorded as waived in the pull request body, but only when this change did not cause it and does not point to it in a comment as handled elsewhere. See What gets recorded instead of filed. Security findings are never filed publicly. They become issues only when the repository is private. Otherwise they stay in a local record, with secrets removed, under .gaia/local/audit/security/, and you are told only how many there were and where the records are.

An audit-loop-unit sub-agent runs the audit rounds a few at a time, outside your main session. In each round, the members review, a fixer repairs what they found, a script checks the repairs, and the Quality Gate runs before the round’s fixes are committed. Your session holds only a short report per unit, so a long audit does not fill your context.

The loop needs Claude Code 2.1.287 or later. Run claude update if a unit stops and names the version.

Each round’s fixes need reviewing in the next round, so left alone the loop would never stop. A hook limits it on each branch. It checks in with you when any of these happens:

  • Your session’s context reaches the red band on the statusline’s context bar
  • Rounds stop making progress (the number of findings caused by the branch stops going down, or each repair causes new findings on the lines it wrote)
  • The maximum number of rounds is reached

At a checkpoint Claude shows you where things stand (rounds run, findings per round, its assessment, what remains by severity, and the cost so far) and asks one question with fixed answers:

  • Continue audit in this session or Continue audit in a new session: allow one more batch of rounds. The new-session option prints a continuation prompt in a code block. Run /clear, then paste it.
  • Accept the remainder: offered only when the loop has a reason to stop (for example, round 10 is reached, rounds stop making progress, or only Minor findings remain from round 6 on), no remaining finding is Critical or security-related, and every member’s report for the round came back complete. Runs one last review round with no fixes, and records what remains under ## Accepted residuals (recorded, not fixed) in the pull request body.
  • Stop and file the remainder: files the remaining findings as tech-debt issues and leaves the pull request open.

Only you can answer. If the question is not available, type one of these as the whole prompt:

audit-grant <n>
audit-accept

You can also type audit-accept on purpose to accept when the menu does not offer it. Claude never types, writes, or fakes either line, and a hook keeps Claude from writing to the checkpoint’s saved state. When you are not watching (a session running unattended), Claude never asks and never grants more rounds: it pushes the round’s fixes, leaves the pull request open, and prints the audit-grant line for you to type later.

The branch checkpointA checkpoint comes when the context reaches its line, rounds stop making progress, or the round limit is reached. With nobody watching, Claude pushes the fixes, leaves the pull request open, and prints the audit-grant line. Otherwise Claude shows where things stand and asks one question. Continue allows one more batch of rounds, in this session or a new one. Accept the remainder, offered only when the loop has a reason to stop, no Critical or security finding is left, and every member left readable findings, runs a last review round with no fixes and records the residuals. Stop and file the remainder files the remaining findings as tech-debt issues and leaves the pull request open.A checkpoint: the context line, rounds stopmaking progress, or the round limitAre you watching?Pushes the fixes, leaves the PR open,prints the audit-grant line for youClaude shows where things stand and asks one questionContinue, in thissession or a new oneAccept the remainder,only when offeredStop and filethe remainderOne more batchof roundsA last review round with nofixes, residuals recordedFindings filed as tech-debtissues, the PR stays openNoYes
The branch checkpointA checkpoint comes when the context reaches its line, rounds stop making progress, or the round limit is reached. With nobody watching, Claude pushes the fixes, leaves the pull request open, and prints the audit-grant line. Otherwise Claude shows where things stand and asks one question. Continue allows one more batch of rounds, in this session or a new one. Accept the remainder, offered only when the loop has a reason to stop, no Critical or security finding is left, and every member left readable findings, runs a last review round with no fixes and records the residuals. Stop and file the remainder files the remaining findings as tech-debt issues and leaves the pull request open.A checkpoint: the context line, rounds stopmaking progress, or the round limitAre you watching?Pushes the fixes,leaves the PR open,prints theaudit-grant line foryouClaude shows where things stand and asks onequestionContinue, in thissession or a new oneAccept the remainder, only when offeredStop and file theremainderOne more batch ofroundsA last review round with no fixes, residualsrecordedFindings filed astech-debt issues, thePR stays openNoYes
The branch checkpoint

Why: a loop nobody watches keeps spending with no limit. And without the checkpoint, “is another round worth it?” would be decided mid-loop by the session least able to judge it. See the gate reference.

Claude posts the GAIA-Audit success status last: once every member involved has approved the current change, every finding from every round is fixed or recorded, and the last push has landed. Posting it any earlier would mark the pull request done while findings were still being decided. Posting success also marks the draft pull request ready for review.

A member’s refusal (it will not approve the change until a finding is dealt with) posts a GAIA-Audit failure straight away. That cancels any earlier success for the same commit, so GitHub’s auto-merge cannot merge the pull request anyway.

Pull requests the audit does not need to review (every changed file is outside the audit’s scope, or a chore(deps) update from /update-deps that only changes package manifests) get the check posted by the merge hook itself, as skipped: out of scope or skipped: chore(deps) manifest-only.

  • ## Audit rounds: total rounds, rounds per member, light reviews, and human grants.
  • ## Accepted residuals (recorded, not fixed): accepted findings, each with a unique key so the same one is not recorded twice. /gaia-residue reads these later.
  • A findings comment, posted on every gh pr merge, carrying every round’s findings from every member. Security and Critical findings are left out of it and appear only as a count.

Each gh pr merge also prints the pull request’s cost.

If Claude tries to merge before the audit is done, the merge hook turns the merge back with one reason and one next step. The refusal goes to Claude, which runs the step it names and tries again; nothing for you to do unless the table says so. The common ones:

Claude seesWhat it meansNext step
A member has no clearance for the current changeThat member has not reviewed the latest commit to its files, or refused itClaude runs the audit loop. A member’s refusal goes away only when the finding is fixed (which changes the reviewed files) or you acknowledge a Major finding with a reason.
The base tip is not a commit in this checkoutThe audit measures your change against the base branch GitHub reports, and your checkout has not fetched itClaude fetches the base branch and retries.
GitHub cannot be reachedThe audit cannot read the pull request’s base branchClaude shows the gh error. If it is a sign-in problem, run gh auth login yourself, since it opens a browser sign-in.
More than one merge base with the base tipGit’s history allows more than one answer to what your branch changedClaude merges the base branch into yours and retries.
A cross-repository (fork) pull requestA local audit would run the fork’s own audit scripts with your credentialsSee Fork pull requests.

If you change a pull request’s base branch on GitHub, the merge hook notices that the base GAIA recorded no longer matches, and Claude resets it and runs the audit again.

Every gate the audit can raise has a row in the gate reference.

GAIA never audits or merges a pull request from a fork in your checkout, and a hook stops Claude from checking out or fetching a fork’s branch. With the fork’s branch checked out, the audit would run the fork author’s hooks and scripts with your credentials. To bring a fork’s change in safely, read what it changes in GAIA’s setup files, copy its commits to a branch in your own repository without checking them out, and audit that branch like any other. The steps, with the prompt and the two commands, are in the gate reference.

Many teams run review, lint, and tests only on CI. GAIA runs them on your machine first, before the pull request merges, and keeps CI as the backstop.

  • The sooner you hear about a problem, the cheaper it is to fix. A problem the Quality Gate or the audit finds is fixed while the change is still fresh, in the same session. A problem CI finds arrives after you have moved on to something else. You wait for the run, look up what failed, switch back, fix it, push, and wait again.
  • You can see and steer it. The audit runs in your Claude Code session, so you watch each round, answer its questions, and stop it or change course. Claude running on CI is a black box: you see the result, not the work, and you cannot adjust it while it runs.
  • It costs less. Each member reviews only what changed since it last approved, and the fixes happen in the same loop. Claude on CI would run its own sessions on top of the one you already have open.
  • Fixing stays simple. On CI, getting Claude to fix what it found and push the fix back takes extra machinery, and that fix needs another review. Locally, the fixer repairs findings inside the audit loop and the next round reviews the repairs.

This was true before AI coding tools, and it matters more with them: Claude writes changes faster than a CI round trip can report on them.

CI still runs. The Tests workflow runs the test suites again on every pull request, and the GAIA-Audit required check confirms the local audit ran. CI is the last check on work that should already be good.

Claude Code’s own review commands, such as /code-review and /security-review, are good reviewers. The audit is a merge gate, though, and a gate needs four things a review command does not give you:

  • A reviewer you can hold steady. Each member’s instructions live in your repository. When they change, that member’s past clearances no longer count and it reviews the change again. A built-in review’s instructions and model change with Claude Code updates, outside your repository, so a clearance it gave last month would still stand after it started checking different things.
  • Every owner signs off. Members are assigned files by path in .gaia/audit-ci.yml, and a change that touches files owned by two members waits until both clear their part. A built-in review is one reviewer over the whole diff.
  • It holds the merge. Claude cannot merge until every member has cleared, and then it posts the required GAIA-Audit check. A local review command only prints findings, and Anthropic’s managed review on GitHub does not block a merge.
  • Every finding is dealt with. Each finding is fixed and checked again, waived with a reason, or filed as a tech-debt issue, and security findings are never posted publicly. Kinds of finding that keep coming back feed /gaia-codify. A built-in review’s findings stay in the conversation.

GAIA also has a cheaper tier for small changes: the light and quick reviews. They use GAIA’s own reviewer rather than a built-in command, because its instructions are versioned in your repository like the members’, and its reply has a fixed format the gate can read.

You can still run any built-in review yourself as a second opinion, before or after the audit. A different reviewer can catch different things. Its findings are advice only: nothing it says clears or blocks a merge. Your project’s wiki records the same reasoning in the “Why not Claude Code’s built-in review” section of wiki/decisions/Code Audit Team.md.

The Code Audit Team reviews each pull request’s diff, so it cannot see a security problem that spans files the diff never touches. For a periodic whole-app pass, you can run cloudflare/security-audit-skill (opens in a new tab), an open-source (MIT) skill, on your own app. It is third-party: GAIA does not install, run, or maintain it, and it is not a Code Audit Team member.

Install it first, following its README, including the sandbox it needs before it runs your app’s code. Once it is installed, ask Claude to run it now and then, for example before a release, not on every pull request. Keep each run scoped and in its quick profile. Runs on the same repository build on each other, so a rerun targets what changed and what earlier runs left open. A scoped quick run on a GAIA project takes about 10 to 19 agents and 15 to 20 minutes.