Checklist · Intermediate · 10 min
Before merging Claude Code's code: a review checklist
Eight checks before merging code written by Claude Code: real tests, secrets, dependencies, licenses, scope, independent review and a bash script.
Tested with Claude Code 2.1.316 · reviewed
See the files on GitHub Free · MIT license
What it's for
Eight checks to run before merging a branch written (fully or partly) by Claude Code: that the tests really run and weren't weakened, that there are no secrets, that new dependencies exist and are justified, that licenses fit, that there are no changes outside the task's scope, whether the AI and CI configuration was touched, an independent review (/code-review, /security-review or a reviewer subagent) and, always, a person who understands the change and approves it. It includes a bash script, review-branch.sh (in the repo's en/ folder), that points out where to look.
The checklist
- Why: the official guide puts it this way: Claude stops when the work looks finished, and "all tests pass" on its own says almost nothing. Always ask for the evidence: the command it ran and its output. And a typical failure in AI code is "fixing" a red test by changing the test: deleting it, skipping it or loosening the assertion.
- How to check it: run the tests yourself on the branch, or look at the PR's CI check; don't rely on the summary alone.
- Deleted tests:
git diff --diff-filter=D --name-only main...HEAD | grep -i test. - Skipped or focused tests in added lines:
@unittest.skip,pytest.mark.skiporxfail,it.skip,describe.only,xit. The script at the end does it for you. - Changed expected values: read the diff of the tests folder. If an expected value changed to match the new result, that's only correct if the behavior was supposed to change.
- New tests for new code: a bug fix without a test that reproduces it breaks again.
- Prevent it: so Claude can't finish with broken tests, the project template ships a Stop hook that runs them every time Claude tries to end its turn.
- Why: generated code sometimes leaves a "temporary" sample key, copies a token from the environment into a config file or writes credentials into a test. Once merged and pushed, the secret is in the history even if you delete it later: you have to rotate it.
- How to check it: search the added lines for words like
api_key,secret_key,token,passwordorBEGIN … PRIVATE KEY:git diff main...HEAD | grep -vE '^\+\+\+' | grep -niE '^\+.*(api[_-]?key|token|passw(or)?d|PRIVATE KEY)'. - No .env in git:
git diff --name-only main...HEAD | grep -E '(^|/)\.env'. - For a serious scan: use a dedicated secret-detection tool in CI (gitleaks, for example) or your platform's secret scanning.
- Prevention is better: with deny rules for
Read(./.env)and a hook that blocks reading secrets (the safety hooks recipe), Claude never sees the value and can't copy it. The project template's CI step fails if a.envis committed.
- Why: an assistant may solve something with a little-known package, with one that doesn't exist (and that someone could register later under that name) or bump a version without you asking. Every dependency is maintenance and attack surface.
- How to check it: list the manifests that were touched (
package.json, lock files,requirements*.txt,pyproject.toml,go.mod,Cargo.toml…); the script does it. - For each new package: was it needed? Does the standard library or something you already use do it? Check that it exists and that it's the one you think (
npm view <package>orpip index versions <package>) and look at its repository, author, downloads and date of the latest release. - Known advisories:
npm auditorpip-audit. - Lock file: a huge change for a one-line change in the manifest deserves a question.
- Prevent it: in CLAUDE.md, "don't add dependencies without asking"; in settings.json,
askfornpm install *orpip install *: an ask asks even in auto mode.
- Why: a new dependency brings its license, and some (strong copyleft ones, for example) may not fit your project or what your company allows. A large block of code that looks copied from another project can also show up.
- How to check it: for each new dependency, look at the license field in its registry (
npm view <package> license) or in its repository. - Lots of dependencies: use a license lister in CI (for example license-checker in Node or pip-licenses in Python) and compare against the list your team accepts.
- Code that doesn't fit: if you see a file or a long function that doesn't look like the rest of the code, with foreign comments or a copyright notice, ask where it comes from before merging it.
- Note: this checklist isn't legal advice; if you work for a company, follow its license policy.
- Why: you asked for a bug fix and the branch also brings a three-file refactor, a mass reformat or a config change. Those extras hide bugs in the noise and make the PR impossible to review.
- How to check it:
git diff --stat main...HEAD: does every file relate to the task? If not, ask for it to be split into another branch or reverted. - AI and CI configuration: look closely at changes in
.claude/,.mcp.json,CLAUDE.md,AGENTS.mdand.github/. A change there alters what Claude will be able to do next time (a new allow rule, a hook, an MCP server) or what gets checked on every PR. The script lists them separately. - Worktrees: if you worked with
claude --worktree, review theworktree-<name>branch against the right base: by default the worktree starts from the remote default branch, not from your HEAD. - Prevent it: one task per branch, prompts with clear limits ("don't touch X", "no refactors") and the project template's
askforEdit(/.github/**).
- Why: the official guide explains that giving the same task to another agent doesn't produce a better answer, but a fresh reviewer does help, because it hasn't seen the reasoning that produced the code and isn't swayed by it.
/code-review(or its alias/review): reviews your branch's changes and uncommitted ones looking for correctness bugs. It runs as a background subagent with its own context and brings back the findings. You can point it at a PR (/code-review 1234), a branch (/code-review main...my-branch) or a path, and give it an effort level:lowgives fewer, more certain findings;mediumtomaxwidens coverage. With--fixit applies the fixes, but watch out: those edits stay outside the checkpoints and/rewinddoesn't undo them; use git./security-review: analyzes your branch's diff against origin's default branch looking for injections, authentication problems or data exposure. It needs anoriginremote.- A read-only reviewer subagent (like the project template's
revisor.md) against a spec: "use a subagent to review the diff against SPEC.md, check that every requirement is implemented and that edge cases have tests, report only correctness failures and skip style". Don't drop the "skip style" part: a reviewer asked to find problems always finds something. More on this in subagents.
- Why: AI review filters, but it doesn't decide. Claude Code's security docs say it bluntly: you're responsible for reviewing proposed code and commands before approving them. And if the person who asked for the change can't explain what it does, it isn't ready to merge.
- How to check it: whoever opens the PR can explain in two sentences what changes and why, and how they tested it (with the output). The project template's
/preparar-prskill drafts that description. - Risk areas: read the whole diff in authentication, permissions, money, customer data, database migrations and deployment. There, approval is always human.
- Edge cases AI tends to skip: null or empty values, zero, empty lists, network errors, unvalidated input.
- Main branch protection: turn it on in your platform, requiring CI to pass and at least one approved review before merging.
- Attribution: if you want to know which commits Claude made, by default it adds an attribution line to its commits and PRs; the
attributionsetting changes or hides it.
- Why: even if everything above goes well, something will slip through eventually. What matters is being able to undo it quickly.
- Small branches: merge one task each. A 2,000-line PR generated in an afternoon doesn't get reviewed: it gets approved blindly.
- Revertible history: prefer one you can revert in one go (
git revert <commit>or a squash merge per PR). - Don't rely on
/rewindas the rollback plan for merged work: it undoes the file-tool edits from that conversation, but not changes made only with Bash, by subagents or by/code-review --fixin the background. Git does. - After merging: check the main branch's CI and, if you deploy, the logs of the first deployment.
The script: where to look before merging
review-branch.sh (en/review-branch.sh in the repo) doesn't replace any of the above: in a few seconds it tells you where to look first (files, possible secrets, dependencies, skipped tests and AI configuration). Save it in your repo or on your PATH, make it executable with chmod +x review-branch.sh and run it from the branch you want to merge:
#!/usr/bin/env bash
# Quick review of a branch before merging: ./review-branch.sh [base-branch]
# It doesn't replace human review: it tells you where to look first.
set -euo pipefail
base="${1:-main}"
range="$base...HEAD"
git rev-parse --verify -q "$base" >/dev/null || { echo "Base branch '$base' does not exist" >&2; exit 1; }
echo "== Changed files (are they all related to the task?)"
git diff --stat "$range"
echo; echo "== Possible secrets in added lines"
git diff "$range" | grep -vE '^\+\+\+' | grep -niE '^\+.*((api|secret|private)[_-]?key|token|passw(or)?d|BEGIN [A-Z ]*PRIVATE KEY)' || echo "(nothing in sight)"
echo; echo "== Dependencies touched"
git diff --name-only "$range" | grep -E '(^|/)(package(-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|requirements[^/]*\.txt|pyproject\.toml|uv\.lock|poetry\.lock|go\.(mod|sum)|Cargo\.(toml|lock)|Gemfile(\.lock)?|composer\.(json|lock))$' || echo "(none)"
echo; echo "== Tests deleted, skipped or focused"
git diff --diff-filter=D --name-only "$range" | grep -iE 'test' || true
git diff "$range" | grep -nE '^\+.*(@unittest\.skip|pytest\.mark\.(skip|xfail)|\b(it|describe|test)\.(skip|only)\(|\bx(it|describe)\()' || echo "(none added)"
echo; echo "== AI and CI configuration touched"
git diff --name-only "$range" | grep -E '^(\.claude/|\.mcp\.json|CLAUDE\.md|AGENTS\.md|\.github/)' || echo "(nothing)"
Real output on a branch with four planted problems (Nov 1, 2026). The numbers on the findings are line numbers in the git diff output, not in the file:
$ ./review-branch.sh main
== Changed files (are they all related to the task?)
CLAUDE.md | 1 +
requirements.txt | 1 +
src/app/__init__.py | 1 +
tests/test_app.py | 1 +
4 files changed, 4 insertions(+)
== Possible secrets in added lines
21:+API_KEY = "sk-fake-123"
== Dependencies touched
requirements.txt
== Tests deleted, skipped or focused
33:+ @unittest.skip("later")
== AI and CI configuration touched
CLAUDE.md
The independent review inside Claude Code:
/code-review # your branch + uncommitted changes, in a separate subagent
/code-review low main...my-branch # few findings, the most certain ones
/code-review high 1234 # a pull request, more coverage
/security-review # diff against origin's default branch
# Against the spec, with the project template's reviewer subagent:
use the revisor subagent to review git diff main...HEAD against SPEC.md: check that every requirement
is implemented and that edge cases have tests; report only correctness failures and skip style
If you also want every PR reviewed automatically on GitHub, add the PR review with GitHub Actions recipe.
Common mistakes
- Merging because Claude said "all tests pass" → without the command and its output you don't know what ran → run the tests yourself or check the CI.
- The red test "got fixed" and nobody looked at how → it was deleted, skipped or its assertion loosened → review the tests folder diff and run
review-branch.sh. - Deleting a secret in a later commit → it's still in the history → rotate the secret.
- Approving a package that "sounds right" → it may not exist or may not be the one you think →
npm vieworpip index versions, and its repository. - Ignoring a change in
.claude/or.github/inside a PR about something else → it changes what Claude can do or what gets checked on every PR → split it out and review it separately. - Using
/code-review --fixand then/rewind→ those edits stay outside the checkpoints → commit first and undo with git. - Reviewing a worktree branch against your HEAD → the worktree starts from the remote default branch → compare against the right base.
What we tested and what's pending
On this PC (Linux, Claude Code 2.1.316): the script (Spanish original revisar-rama.sh, same logic as review-branch.sh) ran on a branch of a test repo with four planted problems (an API_KEY written in the code, a new requirements.txt, an @unittest.skip and a change to CLAUDE.md) and flagged all four; on a branch with no changes it flags nothing; it detects a deleted test; with a nonexistent base branch it exits with code 1; bash -n confirms the syntax. We didn't test /code-review, /security-review or the reviewer subagent, because they need a logged-in session: their behavior comes from the official docs (code-review-local, commands, best-practices). npm view, pip index versions, npm audit, pip-audit, gitleaks, license-checker and pip-licenses are cited as common tools, without testing them here.
FAQ
Does /code-review replace human review?
No. It finds correctness bugs with a subagent that didn't see the conversation, which makes it useful, but the docs leave the responsibility for reviewing with you. Use it as a first filter and leave final approval to a person, especially for authentication, money, data and deployment.
What's the difference between /code-review and PR review in GitHub Actions?
/code-review runs in your session, locally, and counts against your plan like the rest of the session. The GitHub Actions recipe (claude-code-action) reviews every PR on GitHub automatically. There's also the managed Code Review service for Team and Enterprise plans. You can combine them: locally before opening the PR, automatically on the PR.
Does /code-review read my CLAUDE.md?
Yes, it follows your CLAUDE.md, also in a run with -p. It doesn't read REVIEW.md, which only configures the managed Code Review service on GitHub.
Why didn't the script find a secret that's there?
Because it only looks for typical words in added lines: a key stored in a variable with another name or encoded slips past it. It's a quick warning, not a scanner. For that, add a secret-detection tool to CI.
How do I stop Claude from weakening tests to make them pass?
Tell it in CLAUDE.md ("don't change or skip existing tests without asking"), review the tests/ diff on every PR and ask the reviewer to flag deleted, skipped or loosened tests, as the project template's /preparar-pr skill does.