Gates
A gate is any check on a command, a commit, a pull request, or a merge. Together, GAIA’s gates hold every change to the process a well-run engineering team follows: work happens on its own branch, code is checked before it is committed, every change is reviewed before it merges, and secrets and other sessions’ work stay safe. The gates are on from your first session with no setup.
Most gates steer Claude. GAIA’s Claude Code hooks and permission rules act only on Claude’s own tool calls, never on a command you type in your own terminal. When one fires, the refusal goes to Claude, usually with the allowed form of the command or the next step, and Claude takes that route and carries on. There is nothing for you to do.
A few gates need you: an approval only you can give, a sign-in or a password, or a step Claude is kept from by design. A few others can reach you directly, but only when you act by hand: the git hooks when you commit yourself, pnpm’s install rules when you install a package yourself, and the GAIA-Audit check when you merge on github.com. Each gate’s Who acts line says which kind it is.
A gate is not a judgment of you or of Claude. Each one exists because the problem it prevents happens quietly and costs more to find later than to stop now.
| Group | What its gates do |
|---|---|
| Commits and branches | Keep Claude’s work off the default branch, keep Claude from skipping the git hooks or dropping commits, and check each commit’s code, tests, and message before it lands. |
| Pull requests and merge | Claim the issue, open every pull request as a draft with a checked title, leave pull requests from forks to you, and allow a merge only after the Code Audit Team approves the change. |
| Secrets and protected files | Keep secrets out of what Claude reads and writes, protect the files GAIA generates and the records of audit approvals, and keep Claude from destructive deletes. |
| Worktrees and parallel work | Keep each session in its own checkout and on its own ports, keep maintenance commands in the main checkout, and pause new work while your GAIA version is behind. |
| Dependencies | Keep out package versions that are too new or less trusted than earlier ones, and keep the lockfile machine-written. |
| Updates and setup | Check each GAIA update against its checksum, keep two updates from running at once, run setup only where it belongs, and hold Claude’s guarded calls until jq is installed. |
| Tests and lint | Require the audit’s list of members, a reason on every lint rule that is turned off, and an accessibility check on every story. |
The rest of this page lists every gate: what it stops, why, when it runs, who acts, and how it clears. Who acts reads “Claude” when there is nothing for you to do, “You” when the step is yours, and names the one case that reaches you when it is both. Clear it says what Claude does and spells out only the steps that are yours. A name in parentheses after When is the hook that enforces the gate; see Hooks.
Commits and branches
Section titled “Commits and branches”Commits on the default branch
Section titled “Commits on the default branch”- Stops:
git commitonmainormaster, a plaingit pushfrom them, a push that targetsmain,master, orHEAD, and force-pushes that name them (--force,--force-with-lease, or-f). - Why:
- Commit: work reaches the default branch only through a pull request, where the audit reviews it before it merges.
- Plain push: it would land your local commits on the shared branch without the pull request and audit every change goes through.
- Force-push: the branch’s history is shared, so rewriting it pulls commits out from under every clone and branch built on it.
- A push that targets
main,master, orHEAD: stopped from any branch. Amainormastersource would land that branch’s commits on the remote without the pull request and audit. A bareHEADpushes whatever happens to be checked out, which the guard cannot vouch for. The refusal tells Claude to push the feature branch by its own name and open a pull request.
- When: before the
gitcommand runs (block-main-destructive-git). The hook checks the branch namesmainandmasterthemselves, so a default branch with any other name is not covered. - Who acts: Claude. Nothing for you to do.
- Clear it: Claude creates a feature branch, pushes it by name, and opens a pull request as part of the work lifecycle.
Hard reset that drops commits
Section titled “Hard reset that drops commits”- Stops: Claude running
git reset --hardto any commit other than the one the branch is on, on any branch and whatever the spelling:HEAD~1,HEAD^,@~1, a commit hash, or a branch name. A plaingit reset --hard, or one toHEAD, discards only uncommitted changes and is allowed. - Why: the reset moves the branch to that commit, so every commit after it is dropped, and uncommitted work is thrown away with it. Dropping commits is your decision, not Claude’s.
- When: before the
gitcommand runs (block-main-destructive-git). A permission deny ongit reset --hard HEAD~*in.claude/settings.jsonstays as a backstop in case the hook does not run. - Who acts: You, and only when you really mean to drop commits.
- Clear it: the refusal tells Claude the reset is yours, so Claude asks you instead of running it. To undo a commit and keep the history, Claude can use
git revert <commit>. If you do want to drop the commits, run the reset yourself in a terminal or with the!prefix, since the guard acts only on Claude.
Moving another session’s checkout
Section titled “Moving another session’s checkout”- Stops: a
git checkoutorgit switchthat would move the main checkout off a branch that another Claude session is using there, with an open pull request. - Why: the other session is working on that branch in the same folder. Switching branches swaps its files out from under it in the middle of its task, and it loses its audit round.
- When: before the
gitcommand runs. - Who acts: Claude, and you only when the branch is your own and you really want to switch.
- Clear it: after a merge, Claude uses the worktree cleanup, which never runs
git checkout. If the branch is your own, run the command yourself with the!prefix, since the guard acts only on Claude.
No-verify
Section titled “No-verify”- Stops:
git commitorgit pushwith--no-verify,-non a commit, or-c core.hooksPath=<path>. Each of these skips the git hooks. - Why: the pre-commit hook is the minimum check that keeps code that fails typecheck, lint, or its tests out of history. If Claude could skip it, it would only be a suggestion.
- When: before the
gitcommand runs (block-no-verify). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude fixes what the pre-commit hook reports and commits again. If the flag only appears inside the commit message text, Claude rewords the message.
Pre-commit floor
Section titled “Pre-commit floor”-
Stops: a commit whose staged files fail typecheck, lint-staged (opens in a new tab), or their tests. It also stops a commit when the package registry cannot be read, when GAIA’s generated settings no longer match what they should be, or when a commit that changes the Code Audit Team roster or a member’s agent file fails the roster check.
-
Why: it is the fixed, scripted check that keeps code failing typecheck, lint, or its own tests out of history, no matter what the Claude session decided.
-
When: at
git commit, through the native git hook.githooks/pre-commit. -
Who acts: Claude, and you only when you commit by hand.
-
Clear it: Claude fixes the failures it prints and commits again. If you commit by hand and it stops you, ask Claude to fix what it printed. If no hook runs at all, the clone’s git hooks may be switched off, and nothing reports that at commit time.
/setup-gaiachecks for this on every run and offers to turn them on. Between runs, ask Claude:Check that this clone's git hooks are active, and turn them on if they are not.See Checking a clone.
Commit message format
Section titled “Commit message format”- Stops: a commit message that is not a Conventional Commit (
<type>(<scope>): <subject>), and any commit when commitlint (opens in a new tab) cannot run. - Why: commit messages, branch names, and pull request titles all use the same list of Conventional Commits types. With a squash merge, the pull request title becomes the commit message on the default branch. A type GAIA does not know cannot be checked later on.
- When: at
git commit, through.githooks/commit-msg. - Who acts: Claude, and you only when you commit by hand.
- Clear it: Claude rewrites the message with one of the types in
.gaia/conventional-commits.json. When commitlint is missing, Claude runspnpm installand commits again.
Machine-local references
Section titled “Machine-local references”- Stops: a commit, pull request, or issue whose text cites something only one machine has: a SPEC, UAT, or plan number, or a concrete path under
.notebook/or.gaia/local/. It reads commit messages, the lines a commit adds (prose in full, and code comments that take a whole line), and the title and body of agh pr create,gh pr edit,gh issue create, orgh issue edit. - Why: nobody else can open a document that exists on one machine, and another machine’s SPEC with the same number is a different document. A merged commit or pull request keeps the reference for good.
- When: at
git commit, through.githooks/commit-msgand.githooks/pre-commit, and before theghcommand runs (block-machine-local-ref-gh-text). - Who acts: Claude, and you only when you commit by hand.
- Clear it: Claude writes out what the reference stood for: the behavior, decision, or evidence. A placeholder such as
SPEC-NNNpasses. If you commit by hand and the hook catches one, do the same, or ask Claude to reword the message. See the Notebook.
Quality Gate
Section titled “Quality Gate”- Stops: Claude committing source changes before every step of the gate passes with zero warnings.
- Why: the commit hook checks only the files you staged. The full gate catches what breaks elsewhere, including warnings that would otherwise ship.
- When: before any commit that touches source code or config that affects these checks. This is a rule Claude follows (
.claude/rules/quality-gate.md), not a hook. The pre-commit floor is the part that runs no matter what. - Who acts: Claude. Nothing for you to do.
- Clear it: Claude fixes what it reports and runs it again. See Quality Gate for its steps and when it runs.
Test seen failing first (RED gate)
Section titled “Test seen failing first (RED gate)”- Stops:
git commitof a new test for code with predictable output (utilities, parsers, hooks) when that test has never been seen failing in its current form. - Why: a test that passed from the start may not test anything. See Proving a test can fail.
- When: before
git commit(red-verify-commit-check). Only tests that are new in the commit need a recorded failure; editing or renaming a test that already exists never does. Exempt: stories and Playwright (opens in a new tab) tests, type-only tests, tests whose title is computed (a template literal or atest.eachrow), tests the classifier finds have no predictable result (component interaction, async work, layout), and test files that arrive unchanged from a GAIA release. A component or page test the classifier exempts goes through the worthiness check instead. - Who acts: Claude. Nothing for you to do.
- Clear it: while the test fails, Claude runs that one test file as a command of its own, so the failure is recorded, then makes it pass. A failure is recorded only when Claude runs the test. A command that also puts the implementation back records nothing.
Pull requests and merge
Section titled “Pull requests and merge”Issue claim
Section titled “Issue claim”-
Stops: Claude starting work on a GitHub issue already labelled
in-progress. -
Why: two agents or two people who start the same ticket do the work twice, then fight over the merge.
-
When: before a branch or worktree is created for work that closes an issue. This is a rule Claude follows (
.claude/rules/issue-claim.md), not a hook, so nothing blocks the work mechanically. -
Who acts: Claude, and you only to tell Claude the work is dropped.
-
Clear it: Claude tells you the issue is taken and picks different work. The label comes off when Claude merges the claiming pull request with
gh pr merge, and Claude also removes it when it closes that pull request without merging. On atech-debtissue, the next/gaia-debtrun removes a claim left behind. A merge on github.com leaves the label set, and so does work you stop without saying so. Tell Claude when you drop the work:I'm dropping the work on issue #<n>. Release its in-progress claim.
Draft pull requests
Section titled “Draft pull requests”- Stops: a
gh pr createfor this repository without--draft. - Why: reviewers get notified when a pull request is marked ready, and in a GAIA project that should happen when the audit passes. A pull request opened as ready notifies them before any audit has run.
- When: before
gh pr createruns (block-pr-create-without-draft). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude adds
--draftand runs the command again. When the audit passes, it marks the pull request ready. A private repository where GitHub does not allow drafts is allowed without it.
Pull request title
Section titled “Pull request title”- Stops: a
gh pr createorgh pr editwhose title fails the commit lint once(#<n>)is added to the end, and--fillwith no--title. - Why: a squash merge uses the title as the commit message on the default branch. Without this check, a title that passed every local check could fail CI after the pull request was already open.
- When: before the
ghcommand runs (block-invalid-pr-title). The advisory PR Conventions workflow repeats the check in CI. - Who acts: Claude. Nothing for you to do.
- Clear it: Claude passes a Conventional Commits
--titlethat still fits in 100 characters with the(#<n>)suffix added.
Fork pull requests
Section titled “Fork pull requests”-
Stops: checking out or fetching the branch of a pull request opened from a fork, and auditing or merging one. It also stops a pull request that
ghcannot identify as fork or not. -
Why: the audit runs scripts from the files in your working folder. With a fork’s branch checked out, those scripts become the fork author’s code, running with your credentials.
-
When: before
gh pr checkoutor agit fetchofpull/<n>/headorpull/<n>/merge(block-fork-pr-checkout), and at the start of the merge workflow. -
Who acts: You. Bringing in a fork’s code is a trust decision, and the guard keeps Claude from fetching a fork’s branch.
-
Clear it: read what the fork changes in GAIA’s own files first. Claude can show you that without checking the branch out:
Show me the changes pull request #<n> makes under .claude/, .gaia/, and .github/, without checking it out.If you accept them, bring the branch into your own repository yourself from a terminal, since the guard keeps Claude from fetching a fork’s branch. These two commands copy the fork’s commits to a branch in your repository without checking them out:
Terminal window git fetch origin pull/<n>/head:<branch>git push origin <branch>Do not check the fork’s branch out (
gh pr checkoutorgit switch) at any point: that puts the fork’s hooks and scripts in your working folder, where they run with your credentials. Then ask Claude to open a pull request from<branch>and audit it like any other.
Merge without the audit
Section titled “Merge without the audit”- Stops:
gh pr mergeuntil every Code Audit Team member responsible for the changed files has approved the current version of the change. - Why: unreviewed code reaching the default branch is exactly what the audit exists to prevent.
- When: before
gh pr mergeruns (pr-merge-audit-check). - Who acts: Claude, and you only when a member refuses and you decide to accept its Major finding with a reason.
- Clear it: Claude runs the audit loop, fixes what it finds, and merges once every member approves. Pull requests outside the audit’s scope, and
chore(deps)pull requests that only change dependency manifests, are approved by the hook itself.
Audit base unreadable
Section titled “Audit base unreadable”- Stops:
gh pr mergewhen the gate cannot confirm the base branch it compares the change against: GitHub is unreachable, the latest base commit has not been fetched, or the branch has more than one merge base. - Why: each approval is tied to the branch’s own changes compared with the base GitHub reports. If it cannot confirm that base, the gate cannot tell whether the approved change is the one being merged.
- When: before
gh pr mergeruns. - Who acts: Claude, and you only when
ghneeds you to sign in again. - Clear it: Claude runs the step the refusal names: it fetches the base branch, merges the base into your branch, or checks the
ghconnection, then retries. When you retarget a pull request to a different base branch on GitHub, the gate notices and names the reset, and Claude runs it and audits again. The one step you may need to take yourself is signingghback in withgh auth login, because it asks you to log in.
GAIA-Audit required check
Section titled “GAIA-Audit required check”- Stops: merging a pull request on GitHub until the
GAIA-Auditstatus shows success for its latest commit. - Why: without a required check, anyone with merge rights could use the button on github.com and skip the audit entirely.
- When: on GitHub, at merge. Set up by
/setup-gaia. - Who acts: Claude, and you only when you merge on github.com yourself.
- Clear it: Claude posts the status on every merge it runs, so this check meets you only when you click merge on github.com. Ask Claude to merge it instead. When no audit member needs to review a pull request, the merge hook posts the status as
skipped:.
Audit checkpoint
Section titled “Audit checkpoint”- Stops: the next round of the audit loop.
- Why: each round’s fixes can lead to another round, so a loop nobody watches could keep spending without limit. The decision “is another round worth it” should not be left to the session least able to judge it.
- When: when your context reaches its checkpoint line, when rounds stop making progress, or at the hard limit on rounds (
audit-loop-bound). - Who acts: You. Only you can answer, so Claude cannot decide this one for itself.
- Clear it: answer the question shown, or type
audit-grant <n>oraudit-acceptas your whole message. See the branch checkpoint.
Worthiness verdict
Section titled “Worthiness verdict”- Stops:
gh pr mergewhen a test the pull request changed has no worthiness record for its current content. This covers stories, Playwright tests, and component and page tests the classifier finds have no predictable result. An exact rename is skipped. - Why: these tests cannot be reliably watched failing first, so a worthiness review of each one takes the place of the RED gate. The hook checks only that a record exists for the test’s current content; it does not read the verdict or prove how the review was done.
- When: before
gh pr mergeruns (worthiness-presence-check). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude runs the worthiness review on the changed tests and retries the merge.
Hand-rolled merge wait
Section titled “Hand-rolled merge wait”- Stops: a shell loop that checks
gh pr vieworgh pr checksover and over without readingmergeable. - Why: a loop that only stops on
MERGEDnever stops once the merge becomes impossible. A merge conflict turns it into a loop that runs until you notice and stop it. - When: before the Bash command runs (
block-handrolled-pr-poll). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude uses GAIA’s merge wait instead, which also stops when the pull request can no longer merge.
Secrets and protected files
Section titled “Secrets and protected files”Reading secrets
Section titled “Reading secrets”- Stops: reading
.envand.env.<name>files (but not.env.example), keys, certificates, files whose name containscredential(TypeScript source files such ascredentialService.tsexcepted), and anything under asecretsdirectory, through Read, Grep, or Bash. It also stops plainenvorprintenv, which print every environment variable. - Why: anything Claude reads goes into the session transcript and is sent to the model. Once a secret has been read, it is exposed.
- When: before the tool call runs (
block-sensitive-read). - Who acts: Claude, and you only when a file that holds no secret has a secret-like name.
- Clear it: the refusal tells Claude what to do instead. Claude refers to the variable by name, reads
.env.examplefor the list of names, and asks you for any value that is not secret. To set a value for one command, Claude usesenv NAME=value <cmd>. For a key, certificate, or credential file that holds no secret, Claude asks you to read it out or rename it. Keep real values out of anything Claude reads.
When you turn on Claude Code’s sandbox, its read deny list in .claude/settings.json keeps Claude’s shell commands away from the same files too.
Writing secrets
Section titled “Writing secrets”- Stops: writes that contain an AWS key, a GitHub token, a private key, or a variable with a secret-sounding name set to a real-looking value. It also stops any write to a
.envfile except.env.example. - Why: a secret written into a tracked file is one commit away from the repository’s history.
.envfiles hold local secrets Claude is kept from reading, so a write there would overwrite values it cannot see. - When: before Edit, Write, or MultiEdit runs (
block-secrets-write). - Who acts: Claude for secret values in code, and you for the values in
.env. - Clear it: Claude moves a secret value out of the code and refers to it by name. Putting the real value in your gitignored
frontend/.envis yours, since Claude is kept from.envfiles by design..env.exampletakes placeholder values.
GAIA’s manifest
Section titled “GAIA’s manifest”- Stops: writes to
.gaia/manifest.json. - Why: the manifest lists only the files GAIA ships.
/gaia-fitnessreads it to tell GAIA’s files from yours, so an entry you add could mark your own file as GAIA’s and put it out of reach of fitness fixes./update-gaiaalso replaces the whole file from the release, so the edit would be lost anyway. - When: before Edit, Write, or a Bash command that writes files runs (
block-manifest-write). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude never needs to write it; your files never belong there.
/update-gaiawrites it through a specific exemption.
Generated frontend settings
Section titled “Generated frontend settings”-
Stops: direct edits to
frontend/.claude/settings.json, and a commit when it no longer matches what the generator produces. -
Why: the file is generated from the root
.claude/settings.jsonplusfrontend/.claude/settings.overlay.json, so a hand edit is overwritten the next time it is generated. -
When: before the Edit runs (a permission rule), and at
git commit(the pre-commit hook). -
Who acts: Claude. Nothing for you to do.
-
Clear it: Claude edits the root settings or the overlay instead, regenerates the frontend settings, and stages the result. The edit deny itself carries no message, but the
generated-filesrule loads as soon as Claude reads the file and names the source to change. If a direct edit slips through anyway, the pre-commit hook names the regenerate step, and Claude runs it and commits again. To change a setting, ask:Add <setting> to the root .claude/settings.json and regenerate the frontend settings.
Audit markers and loop state
Section titled “Audit markers and loop state”- Stops: Claude writing the files that record audit approvals, the audit loop’s state, or its context readings, and Claude running by hand the scripts that record your grant.
- Why: if Claude could write them, it could fake an approval or your grant.
- When: before the tool call runs (
block-audit-loop-writeand permission rules). - Who acts: Claude never needs these files. You act only when the audit loop’s state is corrupt.
- Clear it: if a file in the audit loop’s state folder,
.gaia/local/protected/audit-loop/, is corrupt, you move it aside from a terminal outside Claude Code, since Claude is kept from that folder by design. Claude tells you which file and prints the exact command for you to run.
ESLint config
Section titled “ESLint config”- Asks before any edit to an
eslint.config.*file. - Why: a config edit can silence a rule just as easily as it can make an approved migration, and only you know which one you meant.
- When: before Edit or Write runs (
block-eslint-config-edit). - Who acts: You. Claude Code shows you a permission prompt.
- Clear it: approve the prompt when the edit is the one you want, and deny it when it is not.
Destructive deletes
Section titled “Destructive deletes”- Stops:
rm -rfof/, an absolute path outside the allowed scratch folders,$HOME,.,*, dotfile or brace globs,.git,.claudeand anything in it, andnode_modules, plus--no-preserve-root. - Why: each one can delete far more than you meant, in one command, with no undo:
- An absolute path can reach anywhere on your machine, outside the repository. One containing
..resolves somewhere its spelling does not name. $HOMEor~holds far more than this repository, and git cannot restore what lives outside it..wipes the whole working folder,.gitand.claudeincluded.*removes every file and folder in it that is not hidden. Dotfile and brace globs remove.gitand.claude..githolds the repository’s history, and a commit you have not pushed exists nowhere else..claudeholds the hooks, skills, agents, and settings.--no-preserve-rootswitches off rm’s own refusal to delete the filesystem root.
- An absolute path can reach anywhere on your machine, outside the repository. One containing
- When: before the Bash command runs (
block-rm-rf). - Who acts: Claude, and you only when
node_modulesreally has to go. - Clear it: Claude deletes by an explicit relative path, and removes one file under
.claude/with a plainrm <file>(no flags). Claude can never runrm -rorrm -fonnode_modules. For a broken or stale install, the refusal points Claude topnpm install, which repairsnode_modulesin place. When it really has to go, delete it yourself in a terminal, then ask Claude to runpnpm install.
Worktrees and parallel work
Section titled “Worktrees and parallel work”Worktree path mismatch
Section titled “Worktree path mismatch”- Stops: an edit whose path points into a different checkout than the worktree the session is working in.
- Why: an absolute path left over from before the session switched worktrees still points to a real file, just in the wrong checkout. The edit would land there without any warning.
- When: before Edit, Write, or MultiEdit runs (
block-worktree-path-mismatch). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude redoes the edit with the path inside the session’s worktree.
Main-checkout-only commands
Section titled “Main-checkout-only commands”- Stops:
/setup-gaia,/gaia-debt, and the maintenance commands when run from inside a linked worktree. - Why: a linked worktree holds another task’s work. These commands set up the clone or create their own branch, and must not write onto another task’s branch.
- When: at the start of the command.
- Who acts: You, since you started the command.
- Clear it: a session cannot leave its worktree, so the refusal asks you to move it. Open Claude Code in the main checkout the refusal names and run the command there. The commands that change files then make their own worktree.
Lagging GAIA version
Section titled “Lagging GAIA version”- Stops: starting new work (a SPEC, a plan, a debt drain, a maintenance run, ad-hoc work) while your checkout’s GAIA version is behind the remote’s.
- Why: the new work would branch from an out-of-date default branch.
- When: at the start of the command. Resuming earlier work is never stopped.
- Who acts: You, with one choice. In auto mode Claude makes it for you.
- Clear it: Claude asks once; choose Pull now and restart, and Claude pulls and starts the command again. For a command only you can start, Claude prints it for you to paste. A command started in auto mode picks the pull without asking. Inside a linked worktree there is no pull option: open Claude Code in the main checkout, run
/update-gaiathere to pull, then start the command again.
-
Stops:
pnpm dev,pnpm storybook, orpnpm pwon a port another checkout is using, and in a worktree that has no port file. -
Why: if two servers slide onto each other’s ports, you end up testing the wrong checkout’s code.
-
When: at server start.
-
Who acts: Claude, and you only when another checkout’s server holds the port.
-
Clear it: in a worktree with no port file, Claude runs the setup step the refusal names. To start it yourself, ask:
This worktree has no port file. Set it up again.Claude asks you before stopping a server that another checkout owns, or one it cannot identify.
Audit in an unprovisioned worktree
Section titled “Audit in an unprovisioned worktree”-
Stops: recording a Code Audit Team member’s clearance in a worktree that was never set up.
-
Why: the clearance would land inside the worktree, where the merge gate never reads it, so the audit round would end with no verdict.
-
When: when the audit loop records a member’s result.
-
Who acts: Claude. Nothing for you to do.
-
Clear it: Claude runs the setup step the refusal names, then runs the member again. To start it yourself, ask:
Set this worktree up again, then re-run the audit.
Dependencies
Section titled “Dependencies”Release-age window
Section titled “Release-age window”-
Stops: installing a package version published less than 3 days ago (
minimumReleaseAge: 4320, strict). -
Why: publicized npm compromises have been pulled within hours, mostly within a day. Three days covers that range, with extra room for a release published just before a weekend.
-
When: at
pnpm installorpnpm add. -
Who acts: Claude, and you only when you need a version sooner.
-
Clear it: Claude installs an older, mature version instead, and
/update-depsskips versions inside the window on its own and picks them up on a later run. Make an exception only when you need a version sooner and you have checked where it came from. Then ask Claude to add it to the exception list inpnpm-workspace.yaml:I have checked where <package>@<version> came from. Add that exact version to the release-age exception list in pnpm-workspace.yaml, with a comment saying who checked it and why.
Trust downgrade
Section titled “Trust downgrade”-
Stops: installing a package version with a lower trust level than earlier releases of the same package (
trustPolicy: no-downgrade). The trust level comes from npm provenance: a signed record of how and where a version was built and published. -
Why: a package that used to ship with provenance and suddenly does not is a sign of a possible account takeover.
-
When: at every
pnpm installorpnpm add, checked against the whole lockfile, including--frozen-lockfileruns in CI. -
Who acts: Claude, and you only to approve an exception for a known-good older release.
-
Clear it: Claude picks a version with equal or better provenance. When the flagged version is a known-good old release from before the package added provenance, ask Claude to add it to the trust exception list in
pnpm-workspace.yaml:<package>@<version> is a known-good release from before the package added provenance. Add that exact version to the trust-policy exception list in pnpm-workspace.yaml, with a comment naming the package that requires it.
Lockfile edits
Section titled “Lockfile edits”- Stops: Claude editing
pnpm-lock.yamlby hand. - Why: hand edits often leave the lockfile broken or out of step with itself. pnpm (opens in a new tab) rewrites it correctly.
- When: before the edit runs (a permission deny in
.claude/settings.json). - Who acts: Claude. Nothing for you to do.
- Clear it: Claude changes
package.jsonorpnpm-workspace.yaml, then runspnpm install,pnpm add, orpnpm removeand lets pnpm rewrite the lockfile. Thegenerated-filesrule loads when Claude reads the lockfile, so Claude has this step before it tries an edit.
Updates and setup
Section titled “Updates and setup”/update-gaia checksum
Section titled “/update-gaia checksum”- Stops: an update whose release bundle does not match its published
.sha256checksum, or has no checksum. - Why: a damaged or tampered bundle would be merged straight into your Claude setup and hooks.
- When: before the update question, in
/update-gaia. - Who acts: You. The run stops and deletes the bad download.
- Clear it: run
/update-gaiaagain for a fresh download. If that one also fails the checksum, the release is damaged or has been tampered with; report it to GAIA.
1.x baseline
Section titled “1.x baseline”- Stops:
/update-gaiafrom a GAIA 1.x project. - Why: 2.0.0 moved the app into
frontend/. Merging that into a 1.x project would make every app file look deleted by GAIA, and the update would offer to delete your app. - When: before any download or change.
- Who acts: You.
- Clear it: follow Upgrading from 1.x.
Notebook not moved
Section titled “Notebook not moved”-
Stops: the spec and plan commands on a machine that still has the 1.x specs or plans records and has not run the one-time move into
.notebook/. -
Why: a session must not hand out new SPEC or plan numbers while the move is pending, or work from a half-moved notebook.
-
When: at the start of the command. Claude stops, reports the one-line reason, and runs nothing else.
-
Who acts: You, to say go.
-
Clear it: from the main checkout, ask Claude to run the move once:
Run GAIA's one-time notebook move and show me the report.The move renames the files into place, merges the old records into the new catalog, keeps the old records as backups, and deletes nothing. If the move itself stops, Claude follows the cause and fix it prints. See Upgrading from 1.x.
Teammate’s update in progress
Section titled “Teammate’s update in progress”- Stops: your
/update-gaiawhile a teammate’s update is in progress. - Why: two people updating at once produce two conflicting update pull requests.
- When: before any download, branch, or worktree.
- Who acts: You.
- Clear it: wait for the teammate’s update pull request to merge or close, or talk to them; the refusal names the pull request and its author.
Outside a GAIA project
Section titled “Outside a GAIA project”- Stops:
/gaia-initanywhere but the root of a project created bynpx create-gaia. - Why: init renames the project, removes GAIA branding, and rewrites files throughout the folder it runs in. Without GAIA’s manifest there is no GAIA project to set up, and in the GAIA source itself it would rewrite GAIA.
- When: at
/gaia-init’s firstgaia initstep, which runs after pnpm, the dependency install, and your answers to the setup questions. - Who acts: You, since you started the command.
- Clear it: run it in a project created with
npx create-gaia.
jq not installed
Section titled “jq not installed”- Stops: any Claude tool call a guard hook would check, while jq (opens in a new tab) is not installed. A call the guard can tell it does not cover, such as the command that installs jq, still goes through.
- Why: the guard hooks read each call with jq. A guard that cannot read the call would otherwise let it through unchecked, so the guards would quietly switch off.
- When: before the tool call runs, in the guard hooks.
- Who acts: You. jq is a system package, and installing it can ask for your password.
- Clear it: install jq, for example with
brew install jqon macOS or your Linux package manager, then ask Claude to retry.
Tests and lint
Section titled “Tests and lint”Code Audit Team roster
Section titled “Code Audit Team roster”-
Stops: the audit when
.gaia/audit-ci.ymlhas noauditors:block. -
Why: with no roster, there is no way to work out which members must review the change.
-
When: when the audit works out which members to run.
-
Who acts: Claude, and you only to confirm members you added yourself.
-
Clear it: the refusal tells Claude to restore the block from the copy GAIA ships, and Claude does. If you had added members of your own, check 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.
Gaps in coverage do not stop the audit: a file no member covers goes to code-audit-frontend, the default member. A commit that changes the roster runs the roster check, which reports files no member covers and patterns that match no file, and Claude repairs what it finds. See Covering new code.
eslint-disable reason
Section titled “eslint-disable reason”- Stops: an
eslint-disablecomment with no-- reasonafter it (@gaia-react/lint3.0.0). - Why: a later reader, human or agent, needs to know why the rule was turned off at that spot. Without the reason, nobody can tell whether the disable is still needed or safe to remove.
- When: at lint, so at
git committhrough the pre-commit hook and in the Quality Gate. - Who acts: Claude. Nothing for you to do.
- Clear it: Claude fixes the code so the rule passes, or adds the reason:
// eslint-disable-next-line <rule> -- <why>.
Story accessibility opt-out
Section titled “Story accessibility opt-out”- Stops: a story that skips its accessibility check, unless its id and a reason are recorded in
frontend/test/story-a11y-opt-out.test.tsx. A story skips the check when it setsparameters.a11y.testto'todo'or'off', setsparameters.a11y.disable, turns on thea11y.manualglobal, has a!testtag, or usesa11ycontext, config, or options that differ from.storybook/a11y.ts. - Why: this check is the only light-theme accessibility scan a story gets; the Playwright scan covers dark. A story that quietly opted out would have no light-theme check at all.
- When: in the Storybook (opens in a new tab) test run, so at
git commitfor staged stories and in the Quality Gate. - Who acts: Claude. Nothing for you to do.
- Clear it: Claude fixes the markup so the story passes. If the opt-out is truly needed, Claude adds the story id and its reason to the test’s opt-out list.