"Self-maintaining" is a seductive phrase, so let's be honest about it up front. You're not going to walk away for a month and come back to a repo that grew new features on its own. What you can build is a loop where Claude does most of the mechanical work — turning issues into pull requests (PRs) — and a separate Claude pass reviews that work before it ever reaches you. You stay the final gate, but you review a clean, already-critiqued PR instead of a blank diff.
That's the whole trick, and it rests on one principle worth repeating: the Claude that writes the code should not be the Claude that approves it. Keep those two roles apart and the system is genuinely useful. Blur them and you've just built something that rubber-stamps its own mistakes.
Here's the shape of the loop:
Three actors, one human gate. Let's build each piece.
Part 1 — Set the ground rules before you automate anything
Automation amplifies whatever standards you already have. If they live only in your head, Claude can't follow them. So the first artifact isn't a workflow file — it's a CLAUDE.md at the repo root. Both the implementer and the reviewer read it, which is exactly why it's the single most leveraged file in this setup.
Keep it concise and specific. Vague rules ("write clean code") get ignored; concrete ones ("no default exports", "colocate tests") get enforced. Here's a starting point tuned to a Vite/Vitest/React/TS project:
# Project standards
## Stack
- Vite + React + TypeScript. Styling: CSS Modules + Sass.
- Tests: Vitest + Testing Library. E2E lives in `e2e/`.
## Architecture
- Feature-first folders under `src/features/<feature>/`.
Shared primitives go in `src/shared/`. No cross-feature imports
except through a feature's public `index.ts`.
- Components stay presentational; data-fetching and side effects
live in hooks (`useXxx`) or services, never inside JSX.
## TypeScript
- `strict` is on. No `any`, no non-null `!` assertions —
narrow the type instead.
- Named exports only. No default exports.
## Styling
- One `.module.scss` per component, colocated. No global class names.
- Use design tokens from `src/shared/styles/tokens.scss`; no hardcoded
colors or spacing.
## Tests
- Every new component/hook ships with a colocated `*.test.ts(x)`.
- Test behavior via the DOM, not implementation details.
- A PR that changes logic without touching a test should be questioned.
## Review criteria (for automated review)
- Flag: prop drilling past 2 levels, effects with missing deps,
untyped boundaries, dead code, and anything that breaks the
module boundaries above.
- Do NOT flag: formatting (Prettier owns that) or subjective naming.
That last block does double duty: it's your house style and the reviewer's checklist. Write it once, both Claudes obey it.
Part 2 — Let Claude take tasks
Two setup steps, then you can mention @claude anywhere in an issue or PR and it goes to work.
- Install the Claude GitHub App from
github.com/apps/claude(or run/install-github-appinside the Claude Code command-line interface (CLI), which walks you through it). You need to be a repo admin. It requests read & write on Contents, Issues, and Pull requests. - Add your key as a repository secret named
ANTHROPIC_API_KEY(Settings → Secrets and variables → Actions). Never hardcode it in a workflow.
Then drop in a workflow that responds to mentions:
# .github/workflows/claude.yml
name: Claude
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
jobs:
claude:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 15 --model claude-sonnet-5"
Note the version pin: @v1, not @beta. The v1 action auto-detects its mode — no more mode: "tag". CLI-style options (--max-turns, --model) now go inside claude_args rather than being top-level inputs.
Now the task-taking pattern is just a comment:
@claude implement the empty-state described in this issue.
Follow src/features/inventory/ patterns and add Vitest coverage.
@claude fix the TypeError in the dashboard filter — repro in the linked issue.
@claude the tokens.scss migration is half done; finish the remaining components.
Claude reads the issue/PR context, makes the changes on a branch, and opens (or updates) a PR. A checkbox list tracks its progress as it goes.
Model choice is a real knob here. Three models are available today:
| Model | Reasoning depth | Cost | Best for |
|---|---|---|---|
claude-opus-4-8 | gnarly architectural changes | ||
claude-sonnet-5 | everyday implementation (the default) | ||
claude-haiku-4-5-20251001 | small, mechanical tasks |
Reach for Opus on gnarly architectural changes; leave Sonnet as your everyday driver.
Part 3 — Let Claude review
This is the half that makes the project "self-maintaining" rather than just "self-writing." You have two good options; pick based on your plan and how much you want to manage:
| Managed Code Review (Option A) | code-review plugin (Option B) | |
|---|---|---|
| Setup effort | ||
| Plan required | Team / Enterprise | any (including the API directly) |
| Workflow file to maintain | none | one review.yml |
| Runs on | Anthropic's infrastructure | your GitHub runner |
| Trigger | PR open / push / manual | every PR open + update |
Option A — Managed Code Review (least setup, Team/Enterprise plans)
Anthropic ships a managed Code Review product — a research preview on Team and Enterprise plans — that an org Owner enables once, then toggles per repository. When a PR runs, multiple agents analyze the diff in parallel, each hunting a different class of issue, then a verification pass filters out false positives before anything is posted. Findings land as inline comments on the exact lines, with a summary in the review body. When you fix a flagged issue, it auto-resolves the thread.
Per repo you choose the trigger: once on PR open, on every push, or manual (@claude review to start, @claude review once for a one-off). It's billed separately from your application programming interface (API) usage, and you can set a monthly spend cap in org settings. By default it focuses on correctness — production-breaking bugs, not style nitpicks — and you widen its scope by adding guidance files (that CLAUDE.md review block from Part 1).
If you're on a Team/Enterprise plan, this is the lowest-friction path: no workflow YAML (a human-readable config format) to maintain.
Option B — The code-review plugin in your own Action
If you'd rather keep everything in a workflow file (or you're on the API directly), run the official code-review plugin as a step. It triggers on every PR open/update, no @claude mention required:
# .github/workflows/review.yml
name: Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
plugins: "code-review@claude-code-plugins"
prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
Either way, the outcome is the same: by the time you open the PR, it already has a critique attached.
Part 4 — Why the implementer and reviewer must stay separate
This is the part people skip, and it's the part that matters most.
If you let the same Claude session write code and then "check" it, you get sycophancy: it's already committed to its approach and will defend it. Real review needs a fresh context that treats the diff as someone else's work.
The good news is that the setup above gives you separation almost for free:
- Managed Code Review runs its agents on Anthropic's infrastructure, independent of whatever session opened the PR. Different context by construction.
- The plugin action spins up on the
pull_requestevent as its own run, with only the diff and yourCLAUDE.mdin context — it doesn't inherit the implementer's reasoning.
Two rules keep the separation real:
- Never let the reviewer be the author. Don't wire a single workflow that both implements and self-approves. Implementation and review should be distinct runs.
- The bot never merges. Its job ends at "review posted." The merge button is yours.
Part 5 — Recipes for your stack
Once the loop exists, most maintenance becomes a comment or a scheduled prompt.Test generation (Vitest). After Claude implements a feature, ask it to backfill coverage in the same PR:
@claude add Vitest + Testing Library tests for the new
useInventoryFilters hook. Test behavior, not internals.
Colocate as useInventoryFilters.test.ts.
Dependency and maintenance sweeps (scheduled). Run Claude on a cron to keep the repo healthy without you asking:
on:
schedule:
- cron: "0 8 * * 1" # Monday morning
jobs:
maintenance:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: >
Check for outdated dependencies and minor version bumps that
are safe given our lockfile. Open one PR per logical group,
run the Vitest suite, and summarize any breaking changes.
claude_args: "--max-turns 20 --model claude-sonnet-5"
Refactors that respect boundaries. Because your module rules are in CLAUDE.md, a request like "extract the shared date logic into src/shared/date/" lands inside your architecture instead of fighting it.
Security review. There's a dedicated security action (and a /security-review command in the CLI) that scans diffs for SQL (Structured Query Language) injection, cross-site scripting (XSS), authentication flaws, and insecure data handling, posting findings as PR comments. Worth adding on any repo with production code:
- uses: anthropics/claude-code-security-review@main
with:
comment-pr: true
claude-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
One caveat straight from its docs: it isn't hardened against prompt injection, so only run it on trusted PRs (see fork safety below).
Part 6 — Guardrails
The difference between a helpful loop and a liability is entirely in the guardrails.Branch protection is the backstop. Require a human approval before merge, and don't grant the bot auto-merge. If the reviewer and the branch rules are your only defenses, make them non-optional.
Fork PRs are dangerous by default — keep it that way. The pull_request event can't read your secrets from a fork, so the action safely no-ops on untrusted contributions. Only switch to pull_request_target if you fully understand that it exposes secrets, and never check out untrusted fork code in that job. For public repos, enable "Require approval for all external contributors."
Least-privilege permissions. Name only the scopes each workflow needs. GitHub's default token is read-only and resets unnamed scopes to none. If Claude can't post a comment, add exactly the one scope the error names (pull-requests: write) — nothing more.
Cost control. Set --max-turns to cap runaway iteration, add a workflow timeout-minutes, use concurrency limits to avoid parallel pile-ups, and set a monthly spend cap (managed Code Review) or watch API usage. Manual review triggers cost nothing until someone types @claude review. Rough order of magnitude: managed Code Review averages $15–25 per review, scaling with pull-request size and complexity — at any real volume this is a line item worth budgeting, so measure your own before enabling it repo-wide.
The CI-on-bot-commits gotcha. When Claude pushes commits, your other continuous-integration (CI) checks may not fire unless the push comes through the GitHub App (not the generic Actions user). If your tests mysteriously skip on Claude's PRs, this is usually why.
Part 7 — On GitLab
If part of your work lives on GitLab, the pattern transfers: there's a self-hosted Claude integration for GitLab CI/CD pipelines. It's more DIY than the GitHub App (you wire Claude Code into a pipeline job yourself rather than clicking "install"), but the same task-and-review loop applies — a job that implements on a branch, a separate job that reviews the merge request diff, and you approving the MR.
A realistic weekly rhythm
Put together, a week looks like this:
- You file issues as you think of them, tagging the ones you want automated with
@claude. - Claude turns them into PRs during the day; a Monday cron sweeps dependencies.
- A separate Claude reviews every PR the moment it opens, leaving inline comments.
- You spend ten minutes reading pre-reviewed diffs, replying
@claude address the review commentswhere needed, and merging what's ready.
The repo isn't maintaining itself. But the boring 80% — writing the change, covering it with tests, catching the obvious bugs — happens without you, and the interesting 20% is where your attention actually belongs.
