Skip to content

Work lifecycle

Every task Claude does for you follows one lifecycle, whether a command drives it (/gaia-plan, /gaia-debt, a maintenance command) or you just asked for a fix in plain language:

  1. Claim the GitHub issue with an in-progress label, so nobody else starts it. This step runs only when the work closes an existing issue.
  2. Name the branch with GAIA’s shared naming script.
  3. Isolate the work in its own git worktree (a second working copy of the repository) or a feature branch, per your team’s policy, so tasks running at the same time never touch each other.
  4. Commit through the Quality Gate and the commit hooks, with a Conventional Commits message, and open a draft pull request.
  5. Audit the pull request with the Code Audit Team.
  6. Merge. Ad-hoc work waits until you ask. Some commands merge their own pull request as part of the run.
  7. Clean up back to a pulled default branch.

Every task gets a draft pull request, even when you did not ask for one. If you say you will review or commit an edit yourself, Claude leaves it uncommitted on your current branch and none of these steps apply.

The work lifecycleIf the work closes an issue that is already labeled in-progress, Claude tells you. Otherwise it claims the issue. Work with no issue skips the claim. Claude names the branch, isolates the work in a worktree or feature branch, commits through the Quality Gate and opens a draft pull request, and runs the audit. Then the merge depends on the work: a command or plan run merges or asks you, ad-hoc work waits until you ask, and /update-gaia stops for you to merge it. Claude squash-merges and waits with pr-wait-merge. Any result other than MERGED stops and reports. Before cleanup, uncommitted or unpushed work keeps the worktree and branch. Otherwise Claude cleans up back to a pulled default branch.Does the work closea GitHub issue?Is it alreadylabeled in-progress?Claude tells you1. Claim: add thein-progress label2. Name the branch3. Isolate: a worktree ora feature branch4. Commit, open adraft pull request5. AuditWho merges?A command or plan runmerges, or asks youAd-hoc work waitsuntil you ask/update-gaia stops:you merge it6. Squash-merge, thenwait with pr-wait-mergeResult is MERGED?Stops and reports: CONFLICTING,CHECK_FAILED, TIMEOUT, or CLOSEDUncommitted orunpushed work?Keeps the worktree and branch,tells you what is left7. Clean up to a pulleddefault branchYesYesNoNoYou askNoYesYesNo
The work lifecycleIf the work closes an issue that is already labeled in-progress, Claude tells you. Otherwise it claims the issue. Work with no issue skips the claim. Claude names the branch, isolates the work in a worktree or feature branch, commits through the Quality Gate and opens a draft pull request, and runs the audit. Then the merge depends on the work: a command or plan run merges or asks you, ad-hoc work waits until you ask, and /update-gaia stops for you to merge it. Claude squash-merges and waits with pr-wait-merge. Any result other than MERGED stops and reports. Before cleanup, uncommitted or unpushed work keeps the worktree and branch. Otherwise Claude cleans up back to a pulled default branch.Does the work close aGitHub issue?Is it already labeledin-progress?Claude tells you1. Claim: add thein-progress label2. Name the branch3. Isolate: aworktree or a featurebranch4. Commit, open adraft pull request5. AuditWho merges?A command or plan runmerges, or asks youAd-hoc work waitsuntil you ask/update-gaia stops:you merge it6. Squash-merge, thenwait withpr-wait-mergeResult is MERGED?Stops and reports:CONFLICTING,CHECK_FAILED,TIMEOUT, or CLOSEDUncommitted orunpushed work?Keeps the worktreeand branch, tells youwhat is left7. Clean up to apulled default branchYesYesNoNoYou askNoYesYesNo
The work lifecycle

When a task is big enough to hand off, Claude splits it across sub-agents. A sub-agent is a separate Claude instance with its own fresh context that reports back to your session when it finishes. Each one gets a single role:

RoleWhoWhat it does
AdvisorA read-only sub-agentInvestigates and writes its findings to a file. It changes none of your project’s code.
DeciderYour sessionReads the findings, decides what to do, and asks you when the choice is yours.
ExecutorA sub-agentEdits files, and nothing else.
VerifierA check after the executorsCompares the executors’ changes with the plan before anything is committed.

Only your session runs git commands that change the repository: staging, committing, pushing, and creating branches. Advisors and executors can read git history but never change it. When a commit needs the Quality Gate, your session runs it once, after that commit’s executors finish and the verifier passes.

Why:

  • Wide reading happens in the advisors, so your session’s context holds the decision instead of the search.
  • Executors share one working copy. A single git command from one executor could quietly discard another executor’s unsaved edits.
  • Running the gate once per commit, not after every executor, avoids paying for a slow check several times and checking half-finished work nobody will commit.

Some work never leaves your session at all. See How GAIA picks a model.

Two exceptions:

  • A command with its own steps follows them where they differ. A /gaia-plan run, for example, commits once per phase.
  • In the audit loop, the audit-loop-unit sub-agent commits and pushes its own fix rounds and runs the Quality Gate for them, so that reading does not land in your session. A script checks every round’s repairs, and a hook caps how many rounds it can run. Your session still decides the checkpoint and the merge.

The workflow-doctrine-inject hook adds these rules to your session when it starts working on a branch or in a worktree.

Claiming applies only when the work closes an existing GitHub issue. Work with no issue behind it, such as a quick fix you asked for in a prompt, skips this step.

When there is an issue, Claude reads its labels first. An issue already labeled in-progress belongs to someone else, so Claude tells you instead of competing for it. Otherwise it claims the issue by adding the in-progress label.

The claim is released automatically when the pull request that closes the issue (with Closes #<n> in its body) merges through gh pr merge in a Claude session. Claude also releases it when it closes that pull request without merging, unless a replacement pull request takes over the same issue. A merge on github.com leaves the claim set. When you drop the work, tell Claude, and it releases the claim:

I'm dropping the work on issue #<n>. Release its in-progress claim.

Why: when two agents or two people start the same issue, the work gets done twice and the two versions then conflict at merge. See the gate reference.

A tech-debt issue is never claimed by hand: run /gaia-debt <n> instead, which owns that claim. See /gaia-debt.

Claude never makes up a branch name. One shared GAIA script generates every branch name GAIA creates, in the form <kind>/<rest>:

WorkBranch
Ad-hoc work<type>/[<issue>-]<slug>, for example fix/142-login-redirect
A plan from a SPEC<type>/spec-<nnn>-<slug>
A plan with no SPEC<type>/plan-<nnn>-<slug>
A tech-debt fix (/gaia-debt)debt/<n>-<slug>, or debt/<a>-<b>-batch for a batch
Maintenance commandsdeps/, update/, wiki/, cleanse/, codify/, fitness/, or residue/ plus a timestamp

Why: GAIA reads branch names back as well as writing them. The prefix tells it which workflow made a branch and which issue or SPEC it belongs to, so a resumed plan or /gaia-debt can find its own work. One script for both writing and reading means the two can never disagree.

<type> is the Conventional Commits type of the change (feat, fix, chore, and the rest). The pull request title uses the same type, and the advisory PR Conventions check compares them. See Git hooks and conventions.

A git worktree (opens in a new tab) is a second working copy of the same repository, in its own folder and on its own branch, sharing one history. By default, GAIA gives every task its own worktree. Your team can change that with the isolation policy. This means:

  • Tasks run side by side. A plan, a bug fix, and a dependency update can each run in their own session at the same time. None of them sees another’s edits, and none of them touches the checkout you are working in.
  • Your checkout stays yours. It keeps its branch and your own edits while a task runs in its worktree. Claude never stashes your work to start a task.
  • Each worktree is ready to use. GAIA links the shared files, installs dependencies, and gives each worktree its own ports, so the app, Storybook, and tests can run in several worktrees at once. See Worktrees provision themselves and Ports per worktree.
  • GAIA works the same in every worktree. Hooks, gates, and the audit work in a worktree the same way they do in the main checkout. GAIA is designed so that nothing done in one worktree blocks another or is attributed to it.
  • Cleanup is safe. After the merge, Claude removes the worktree, unless it still holds uncommitted or unpushed work. See Clean up.

A few commands are the exception: they run only from the main checkout. See Commands that run only in the main checkout.

Your team’s isolation policy lives in .gaia/project.json as isolation_policy:

PolicyWhat happens
always-worktree (default)Every task runs in its own git worktree (a separate working copy of the repository), so tasks can run at the same time without touching each other or your checkout. Claude does not ask.
prefer-worktreeClaude suggests a worktree and you can choose a branch instead.
prefer-branchClaude suggests a feature branch in your current checkout and you can choose a worktree instead.

A repo admin changes the policy with /setup-gaia --reconfigure. Saying “in a worktree” or “on a branch” in your request answers the question up front. Under always-worktree, though, a request for a branch still gets a worktree, and Claude tells you so.

GAIA uses Claude Code’s built-in worktrees under .claude/worktrees/. When a session enters one, GAIA renames its branch to the generated name and sets the worktree up:

  • Shared state is linked, not copied. GAIA’s machine-local state (.gaia/local), the Notebook, and the main checkout’s frontend/.env are linked into the worktree. Nothing written there disappears when the worktree is removed, and your secrets stay in one file instead of being duplicated into every worktree. Separately, Claude cannot read .env files in any checkout. See the gate reference.
  • Dependencies install and typed routes are generated in the background. A new worktree has no node_modules, so GAIA runs pnpm install --frozen-lockfile in it. It then runs React Router’s typegen, which writes TypeScript (opens in a new tab) types for each route’s URL params and loader data, so route code type-checks against the worktree’s own routes. See React Router’s type safety guide (opens in a new tab). Both steps run as a background job so the session does not wait for them, and Claude is told where the job’s log is while it runs.
  • Each checkout has its own ports. See Ports per worktree.
  • A guard keeps edits in the right copy. If Claude aims an edit at a different checkout from the session’s worktree, a guard sends it back, and Claude redoes the edit with the worktree’s path. When a session moves into a worktree, Claude may still hold absolute paths it used in the main checkout. Those paths still point to real files, just in the wrong copy, so an edit through one would change the main checkout without any warning. See the gate reference.

If a worktree’s links break, ask Claude to repair them:

This worktree's GAIA links are broken. Repair them.

What a worktree holds, next to the main checkout:

  • Directorymy-app/ the main checkout
    • Directory.gaia/
      • Directorylocal/ GAIA’s machine-local state, the only copy
        • …
    • Directory.notebook/ the only copy
      • …
    • Directoryfrontend/
      • .env your secrets, the only copy
    • Directory.claude/
      • Directoryworktrees/
        • Directorymy-task/ one worktree
          • Directory.gaia/
            • Directorylocal/ a link to the main checkout’s copy
              • …
          • Directory.notebook/ a link
            • …
          • Directorynode_modules/ its own install
            • …
          • Directoryfrontend/
            • .env a link
            • Directorynode_modules/ its own install
              • …
            • .gaia-ports this worktree’s ports, for example 5174 and 6007

Every checkout gets its own dev, Storybook (opens in a new tab), and Playwright (opens in a new tab) ports, so you can run the app in several worktrees at the same time without one server taking another’s port.

  • The main checkout keeps the default ports: 5173 (dev) and 6006 (Storybook).
  • Each worktree gets a numbered slot, the lowest one free, and its ports are the defaults plus that number. Slot 1 is 5174 and 6007, slot 2 is 5175 and 6008. Slots cover the frontend/ package only.
  • A worktree keeps its slot every time a session returns to it. The one exception is a slot that a live worktree of another GAIA project also holds, which can happen, for example, with worktrees created before GAIA shared slots across projects. Then the worktree moves to the lowest free slot and GAIA rewrites its port file.
  • pnpm dev, pnpm storybook, and pnpm pw use the worktree’s ports automatically. They read them from frontend/.gaia-ports, which GAIA rewrites each time a session enters the worktree, so don’t edit it. To see a tree’s ports, ask Claude: Which ports does this worktree use?
  • A taken port stops the server, never swaps it. A server will not start on a port that is already in use instead of quietly picking another one, so you never end up testing another checkout’s server by mistake. Playwright reuses a running server only when it belongs to its own worktree. A direct storybook dev skips this check and can move to another port, so start Storybook with pnpm storybook.
  • A worktree with no port file will not start pnpm dev, pnpm storybook, or pnpm pw rather than borrow the main checkout’s ports. When Claude hits this, it runs the setup step the message names and starts the server again. If you started the server yourself, ask Claude to fix it: This worktree has no port file. Set it up again.
  • Leftover servers are stopped for you, carefully. At a later session start, GAIA stops servers that a Claude session started once that session has ended, and servers left running by a removed worktree. A server you started yourself is never stopped automatically, and Claude asks before stopping anything on a port another worktree owns.

Every commit passes the commit hooks. A commit that touches source or build configuration also passes the Quality Gate, the full set of checks on the frontend/ package. The branch is pushed and the pull request opens as a draft with a Conventional Commits title. When the work closes an issue, Closes #<n> goes in the pull request body, never in a commit message. A squash merge copies commit messages into the final commit, and GitHub would close the issue from there no matter what the body says.

Why draft: reviewers should be notified when a pull request is ready, and in a GAIA project that is when the audit passes and posts its GAIA-Audit check. A pull request opened as ready notifies reviewers before any audit has run. When the audit passes, it marks the pull request ready. See the gate reference.

Claude audits every pull request before merge, including small one-off changes. The audit is never skipped, though a small change can go to a light review. See Code Audit Team.

For ad-hoc work you did not ask Claude to merge, it stops here and reports the pull request as ready to merge, leaving the branch, worktree, and pull request in place.

Who decides when to merge depends on what started the work:

WorkWhen it merges
Ad-hoc work (a plain request)Only when you ask. A later message asking to merge counts.
A plan run from its kickoff promptAt the end of the run, Claude prints a summary and asks one question: merge it, or hold the merge.
/gaia-debt, /gaia-wiki, /update-deps, /gaia-cleanseAs part of the run, once the audit passes (/gaia-wiki posts its own) and the required checks are green. You do not need to ask.
/gaia-residue, /gaia-codify, /gaia-fitnessThe command asks before it merges, and you can leave the pull request open instead.
/update-gaiaIt opens a draft pull request and stops. You merge it when you are ready.

Claude squash-merges, then waits for the merge to land. The wait ends with one result (MERGED, CONFLICTING, CHECK_FAILED, TIMEOUT, CLOSED). It stops as soon as the pull request has a conflict or a required check fails, instead of waiting forever. Cleanup runs only after MERGED. A hook blocks Claude from writing its own wait loop for the same reason: a loop that waits only for MERGED never ends once the merge can no longer happen.

After MERGED, Claude removes the worktree (or switches off the feature branch), deletes the local branch, fetches, and pulls the default branch. It runs pnpm install --frozen-lockfile only when the pull changed pnpm-lock.yaml.

Cleanup never forces a pull and never stashes. Before removing a worktree it checks for uncommitted files and unpushed commits. If it finds either, it keeps the worktree and the branch, and tells you what is left and how to remove them. When the main checkout is on a branch another session holds, Claude skips the pull and says so.

A worktree that cleanup kept, or one whose remote branch was deleted without the worktree being removed, is listed at every session start, with the commands to remove it. The list shows only worktrees whose work is merged and that no live Claude session is using. GAIA never removes them itself: Claude removes a listed worktree only when you name it.

Work that spans sessions resumes from a small saved state, rewritten in place. Where it goes depends on the work:

  • A /gaia-plan run saves to its own progress files, at a committed phase boundary
  • /gaia-spec saves to its draft cache
  • A research folder saves to its own state file
  • Other work on a branch saves to .gaia/local/runs/<branch>/STATE.md
  • Work on the default branch saves to .gaia/local/runs/session-<session id>/STATE.md

A resumed session reads that saved state and the files it lists, and nothing else. A saved place does not last forever. Once its branch no longer exists, GAIA deletes a branch’s run folder after 7 days. A save made on the default branch is deleted after 30 days.

When the context reaches GAIA’s red band, Claude finishes the current step, saves its place, and asks one question:

  • Continue here: keep working until the next band.
  • Clear and resume: Claude prints a continuation prompt in a fenced code block. Run /clear and paste it.

At 🔥 and 💀, the two fullest bands, Claude always recommends clearing. Sessions running with nobody watching never ask: they save and continue, and at 💀 they save, print the continuation prompt, and stop.

You can also ask Claude for a continuation prompt at any time. It always arrives in a fenced code block, ready to paste.

Where the bands sit, how Claude knows it has reached one, and why GAIA asks so early are on Token efficiency.

Commands that run only in the main checkout

Section titled “Commands that run only in the main checkout”

/setup-gaia, /gaia-debt, and the maintenance commands (/gaia-wiki, /gaia-residue, /gaia-codify, /gaia-fitness, /gaia-cleanse, /update-deps, /update-gaia) stop inside a linked worktree and name the main checkout. Open Claude Code there and run the command again. The ones that change the repository create their own worktree or branch for the work. See the Maintenance overview.

Commands that start new work (SPECs, plans, tech-debt fixes, one-off changes) offer to pull first when your checkout’s GAIA version is behind the remote. Resuming earlier work is never interrupted by that offer.