Review of the CLAUDE.md git workflow

Julien Béranger

+ Claude Opus 5

What was assessed

The 202-line global CLAUDE.md at ~/.claude/CLAUDE.md, which governs every project run through Claude Code. It defines a task-confirmation step, two parallel forge workflows (GitHub and rickub), commit conventions, and a review protocol built on the Git index.

The question was whether it lines up with known best practices. Short answer: the skeleton is orthodox, one part is genuinely invented, and the soft spots are all in verification rather than process design.

What maps to established practice

Rule in CLAUDE.mdKnown practice
Spec-then-confirm before non-trivial workThe explore → plan → code → commit loop described in Anthropic's Claude Code best practices.
Issue → branch off main → PR → checks → squash merge → deleteGitHub Flow, plus issue-first tracking. Short-lived branches off an always-deployable main is trunk-based development.
Never push to main, never force-push a shared branchUniversal.
Never merge on a red or still-running checkStandard CI gating. The gh pr checks --watch discipline is stricter than most people actually manage.
Hand-written CHANGELOG.mdKeep a Changelog, which explicitly argues against generating changelogs from git log. Consistent with the choice not to use Conventional Commits.
Small atomic commits, imperative mood, no trailing periodThe Pro Git contribution guidelines and Tim Pope's note about git commit messages — minus the capitalized subject line.
Author never approves their own chunkSeparation of duties, the four-eyes principle.

Nothing here is idiosyncratic in a bad way. The deviations that exist — lowercase commit subjects, no Conventional Commits prefixes — are internally consistent and cost nothing.

The genuinely novel part

The stage-then-commit loop, where the index is the approval token: whoever did not write a chunk approves it by running git add, and the author then commits exactly what was staged.

This does not appear to be written down anywhere as a named practice. It is a synthesis of two real ones:

  • Pre-commit review, as practised by Gerrit and Phabricator, where nothing enters history unreviewed — as opposed to the GitHub model, where the pull request reviews commits that already exist.
  • Pair programming with role separation, where the non-author validates continuously instead of in one batch at the end.

Repurposing git add as the handshake is the clever bit. It is an out-of-band, asynchronous, zero-tooling signal both parties can observe: no bot, no comment thread, no state file. Forbidding git add -A and git add . closes the obvious loophole.

Where it is actually weak

  1. Squash merge discards the granularity the loop works hard to produce. The file mandates one-logical-change commits, then --squash collapses them into one at merge. The fine-grained history serves review-in-flight only and dies at step 11. Defensible — the staging loop is the review — but it trades away git bisect and git log archaeology on main. Worth being deliberate about rather than incidental.

  2. Partial staging can produce commits that were never tested. "If I stage only part of what you wrote, commit that part" means the committed tree is not the tree that was linted or run — the classic staged-versus-worktree hazard. git stash --keep-index before verifying would close it; whether the friction is worth it is a judgment call.

  3. No test gate, and pnpm build is explicitly banned. pnpm lint is the only pre-done check. On a TypeScript project that lets type errors through to CI, which is the expensive place to find them. And the rule assumes a pnpm project: the repository this review was written in is a Foundry Solidity project with no pnpm lint at all, where forge test is the real gate. The file does not say so.

  4. The review leaves no trace. Because attribution trailers are banned, nothing in the history records that a second party approved a chunk. Reviewed-by: is a different thing from Co-Authored-By: — it asserts review, not authorship — and is the conventional artifact, per Git's SubmittingPatches, if the four-eyes property should ever be auditable rather than merely procedural.

  5. Small ambiguities. Step 6 (changelog) sits outside the 5–7 loop, so later commits never re-touch it — probably intended, but unstated. There is no rebase-versus-merge policy for when main moves under a long-lived branch.

  6. No rule for rejection. The loop covers "stage everything" and "stage part of it", but not "this chunk is wrong, start over". In practice that gets said out loud, but the written protocol has a hole there.

Verdict

A coherent, above-average workflow. The conventional parts are conventional in the right way, and the invented part solves a real problem — reviewing AI-written code before it becomes history rather than after — with mechanics that cost nothing to operate. The weak points cluster entirely in verification, points 2 and 3 above, not in the design of the process itself.

Appendix: the file under review

~/.claude/CLAUDE.md, verbatim as of the date above.

# Task confirmation

Before starting any non-trivial task, rephrase my request in your own
words as a short spec (what you understood, what you're about to do)
and wait for my confirmation. Accept "go", "yes", "y", "yep", "sure",
or anything equivalent as confirmation — don't demand exact wording.

Once confirmed, run the task end-to-end with zero further
interruptions — no permission prompts, no intermediate check-ins —
except the stage-then-commit loop defined in the git workflow below.
Skip this confirmation step for trivial asks (reading a file,
answering a question, a one-line lookup).

# Git & forges

## Attribution

Never add `Co-Authored-By: Claude` or `Generated with Claude Code` to
commits, PR bodies, or issue comments. I am the sole author.

## Choosing a workflow

Run `git remote get-url origin` before anything else, then follow the
matching workflow below:

- host `github.com` → GitHub workflow
- host `git.rickub.com` → rickub workflow
- no remote → commit locally only; ask before adding one

Everything outside the numbered sequences — Attribution, Issues, Pull
requests, Commits, the stage-then-commit loop — applies to both.

## GitHub workflow

Always follow this order. Never skip a step.

1. Check for uncommitted or untracked changes on the current branch.
   If there are any, show a short recap of what they do and ask
   whether to keep them or discard them (via `git stash` — reversible),
   then immediately move on to step 2 without waiting for the answer.
   Resolve the answer by step 3: keep needs no action (the new branch
   carries them forward automatically), discard means stashing first.
   Anything kept goes through the normal stage-then-commit loop (step
   5) alongside the new work.
2. Create an issue
3. Create a branch from that issue, off main
4. Fetch the branch locally
5. Commit
6. Update CHANGELOG.md (create it if it doesn't exist)
7. Push
8. Open a pull request
9. Repeat 5–7 for further commits as the work continues
10. Wait for PR checks to finish and pass
11. Merge
12. Checkout main, sync it, and delete the merged branch

Steps 3–4 collapse into `gh issue develop <number> --checkout`. Open the
PR right after the first commit is pushed (step 8) — don't wait until
the work is finished. Later commits just push to the same branch.

Run the whole sequence end-to-end without pausing to ask permission at
each step — this applies across all projects. The one exception is
step 5 (commit), which does not work like a normal commit.

The rule in one line: **whoever did not write a chunk approves it by
staging it, and the author then commits exactly what was staged.**
Nobody stages their own work, and nothing gets committed unreviewed.

When I write the chunk:

- I write one logical chunk of changes, leave it unstaged, say exactly
  "Please check my changes as I keep on working on the next steps.", and stop — I
  never run `git add` or `git commit` myself before you've staged.
- You review the unstaged diff in your IDE and `git add` what you
  approve.
- I watch for that and commit exactly what's staged, then immediately
  start writing the next chunk as new unstaged changes.

When you write the chunk — you implementing a task, and this covers
source, tests, scripts, docs, config, everything:

- You write one logical chunk, leave it **unstaged**, say in one line
  what it is and that it's ready for review, and stop. Never run
  `git add` on your own work. Not for a doc, not for a script, not for
  a file you consider uncontroversial, and never `git add -A` or
  `git add .`.
- I review the unstaged diff in my IDE and `git add` what I approve.
- You watch for that by polling `git status` — no nudges, no check-ins,
  no asking me whether I'm done reviewing — and the moment something is
  staged, you commit exactly what's staged and nothing else, then
  immediately start the next chunk as new unstaged changes.
- While I'm reviewing I'll often ask questions about the code — why a
  format, why an approach, why that name. A question is not a pause in
  the loop. Answer it, then **check `git status` in that same turn**,
  because I usually stage while or right after I ask. If something is
  staged, commit it before you end the turn. Never end a turn with
  staged changes sitting uncommitted, whatever else the turn was about.
- If I stage only part of what you wrote, commit that part and leave
  the rest unstaged. I'll either stage the rest or tell you to change
  it.
- Don't pile the whole task up into one review. Keep each chunk small
  enough to read in one sitting, and stop after each one.

Repeat until the step's work is done.

Every other step, including push, runs without approval. Still show
what was done (issue #, branch, PR #, merge result) so I can see and
intervene.

Step 10: `gh pr checks <number> --watch`. If a check fails, fix the
underlying issue, commit, and push before merging — never merge on a
red or still-running check, and never skip this step because the diff
looks safe.

Step 11: merge with `gh pr merge <number> --squash --delete-branch`, which
removes the remote and local branch in one go.

Step 12: after merge, `git checkout main && git pull`. If the branch
survived the merge (e.g. `--delete-branch` was not used), delete it:
`git push origin --delete <branch>` and `git branch -d <branch>`. Never
leave a merged branch behind.

## rickub workflow

Same order and the same rules as the GitHub workflow — only the
commands differ, because the `rickub` CLI is not a drop-in for `gh`.

Command mapping:

| Step | GitHub | rickub |
| ---- | ------ | ------ |
| 2 | `gh issue create --assignee @me --label enhancement` | `rickub issue create -t "…" -b "…"`, then `rickub issue assign <n> --user julien` and `rickub issue label <n> --labels enhancement` |
| 3–4 | `gh issue develop <n> --checkout` | no equivalent — `git switch -c <branch> main` |
| 8 | `gh pr create` | `rickub pr create --base main --head <branch> -t "…" -b "…"`, then assign it (see below) |
| 10 | `gh pr checks <n> --watch` | `rickub run list`, then `rickub run watch <number>` |
| 11 | `gh pr merge --squash --delete-branch` | `rickub pr merge <n> --method squash`, then delete the branch by hand |

Notes:

- `rickub issue create` takes only `-t` / `-b`; assignee and labels are
  separate subcommands, so step 2 is three calls, not one.
- Labels are per-repo and start empty. If `enhancement` / `bug` don't
  exist yet, create them with `rickub label` before labelling.
- `rickub pr create` has no `--assignee`, but the assignee rule still
  applies: right after creating the PR, assign it to me through the
  API escape hatch —
  `rickub api POST /repos/<owner>/<repo>/merge-requests/<n>/assignees -F subject=julien`
  (the field is `subject`, not `user`, and PRs live under
  `merge-requests`, not `issues``rickub issue assign` will not reach
  them since issue and PR numbering are separate). Verify with
  `rickub pr view <n> --json`.
- `rickub pr merge` has no `--delete-branch`: after merging, run
  `git checkout main && git pull`, then `git push origin --delete <branch>`
  and `git branch -d <branch>`.
- Branch naming: since there's no `issue develop`, name branches
  `<n>-short-slug` (e.g. `1-add-main-logic`) to keep the issue link
  legible.
- `rickub run watch` exits 0 on success, 1 otherwise — same red/green
  rule as step 10: never merge on a red or still-running check.
- The web UI is `rickub.com/<owner>/<repo>`; git traffic is
  `git.rickub.com/<owner>/<repo>.git`. Don't mix them up in links.

## Issues

- Title starts with a capitalized verb, usually Add / Fix / Improve /
  Remove. e.g. `Add passkey recovery flow`, `Fix stale nonce on retry`.
- If an issue has no main description, write one as the first comment:
  what the task is, why, and what done looks like.
- Assign it to me (`@me` on GitHub, `--user julien` on rickub).
- Label it `enhancement` or `bug`, whichever fits.

## Pull requests

- PR title is identical to the issue title — same verb, same casing.
- Link the issue in the body so merging closes it (`closes #12`).
- Always assign it to me: `--assignee @me` on GitHub,
  `rickub api POST /repos/<owner>/<repo>/merge-requests/<n>/assignees -F subject=julien`
  on rickub.
- Never push to main directly. Never force-push a shared branch.

## Commits

Commits do NOT follow the issue/PR casing. They are lowercase.

- As small as possible — one logical change per commit.
- Very short titles. Lowercase, always — including the first word.
- Imperative mood. No trailing period. No emoji.
- No body unless the change genuinely needs explaining.

e.g. issue `Add passkey recovery flow` → commits `add recovery route`,
`handle expired challenge`.

# Tooling

- pnpm, never npm or yarn.
- Run `pnpm lint` before declaring a task done. Don't run `pnpm build`.

# Style

- Reply in whatever language I'm writing in. Don't switch to English.
- Be terse; skip preamble and closing summaries.
- Don't add comments explaining what the code obviously does.

Further reading

Review of the CLAUDE.md git workflow — Julien Beranger