git-workflow
Git Workflow
Section titled “Git Workflow”Authentication
Section titled “Authentication”Claude Code sets a GITHUB_TOKEN env var with limited scopes (repo only — no workflow). This blocks pushes that include .github/workflows/ files.
Always prefix git push and gh api calls with env -u GITHUB_TOKEN to use the keyring token (which has full scopes) instead:
env -u GITHUB_TOKEN git push origin <branch>env -u GITHUB_TOKEN gh api repos/...GitHub API — REST Only
Section titled “GitHub API — REST Only”Never use gh pr create, gh pr merge, or gh pr view. These use GraphQL (5000 pts/hr, frequently exhausted). Use gh api (REST, separate 5000 req/hr budget) for everything.
# Detect repo slug onceSLUG=$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\.git$||')
# Create PR (REST — replaces gh pr create)env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls" \ --method POST \ --field title="feat(scope): description" \ --field head="feat/my-branch" \ --field base="main" \ --field body="PR body here"
# View PR (REST — replaces gh pr view)env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}"
# Merge PR (REST — replaces gh pr merge)env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}/merge" \ --method PUT \ --field merge_method=squashWhy REST over GraphQL? GraphQL has a separate 5000 points/hr rate limit that gets exhausted during intensive sessions. REST has its own 5000 req/hr budget that never runs out in practice. One code path, no fallbacks needed.
Branch-First Policy
Section titled “Branch-First Policy”Never commit directly to main. All work happens on a branch, gets reviewed via PR, and merges via squash.
main branch → always clean, always deployablefeature work → branch → PR → squash merge → branch deletedBranch Naming
Section titled “Branch Naming”Match the conventional commit type:
| Prefix | When |
|---|---|
feat/ | New feature or capability |
fix/ | Bug fix |
chore/ | Tooling, deps, config |
docs/ | Documentation only |
test/ | Tests only |
refactor/ | Code restructure |
Format: <type>/<short-description> — e.g. feat/auth-refresh, fix/null-checkout
Merge Strategy
Section titled “Merge Strategy”Always squash merge via REST API — one commit per feature on main, clean linear history.
# Merge via REST APISLUG=$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\.git$||')env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}/merge" \ --method PUT --field merge_method=squashCommit Conventions
Section titled “Commit Conventions”- Frequent, atomic commits
- Conventional commit messages (feat, fix, refactor, test, docs, chore)
- TDD order: test commit before (or with) implementation commit
Post-Merge Cleanup
Section titled “Post-Merge Cleanup”After a branch merges, clean up local state:
git checkout maingit pullgit fetch --prunegit branch --merged main | grep -v | xargs git branch -d 2>/dev/null || trueFor squash-merged branches (git can’t detect squash ancestry):
git branch -D feat/my-featureRun node .claude/lib/doctor.js to detect stale local branches.
Integration Branches
Section titled “Integration Branches”When to use: Auto-detect trigger — 3+ active branches touching overlapping files.
Suggest to developer: “Multiple branches touching shared files. Create integration branch to reconcile?”
Developer approves before creation. Never auto-create.
Naming
Section titled “Naming”integrate/<description> — e.g., integrate/auth-refactor
Lifecycle
Section titled “Lifecycle”- Create from main:
git checkout -b integrate/auth-refactor main - Merge constituent feature branches into it
- Test the integrated result
- Merge to main via single squash PR
- Delete integration branch + all constituent branches
When NOT to Use
Section titled “When NOT to Use”- Changes are independent (no file overlap)
- Parallel reviews are wanted (integration blocks individual PRs)
- One risky change shouldn’t hold others back
Conflict Resolution
Section titled “Conflict Resolution”- Rebase frequency: Rebase feature onto main at minimum every 2 days
- Resolve on feature branch: Never resolve conflicts on main
- Complex conflicts: If resolution is complex → create integration branch
- Enable
git rerere: Reuse recorded resolution to avoid re-resolving same conflicts
git config rerere.enabled trueBranch TTL
Section titled “Branch TTL”| Age | Action |
|---|---|
| 7 days | Warning: “Branch feat/x is 7 days old. Rebase or merge?“ |
| 14 days | Flag: “Branch feat/x has drifted significantly from main” |
Detection: doctor.js enhanced to check branch age + main divergence.
TTL check runs during: finishing-a-development-branch, safe-merge.
# Check branch ageBRANCH_DATE=$(git log -1 --format=%ci <branch>)DAYS_OLD=$(( ($(date +%s) - $(date -d "$BRANCH_DATE" +%s)) / 86400 ))worktree — Worktree Management (MANDATORY)
Section titled “worktree — Worktree Management (MANDATORY)”All feature branches MUST use worktree for creation. Raw git checkout -b, git branch <name>, and git switch -c are blocked by enforce-worktree.sh hook. See worktree-discipline rule.
# Create worktreeworktree create feat/my-feature
# Launch parallel agentsworktree launch feat/auth "Add OAuth" &worktree launch feat/api "Build endpoints" &wait
# Monitorworktree status
# Merge via PR + cleanupworktree merge feat/auth
# Cleanup merged worktreesworktree pruneDraft PRs
Section titled “Draft PRs”Support draft PRs for early-signal / WIP visibility:
# Create draft PR via RESTSLUG=$(git remote get-url origin | sed 's|.*github.com[:/]||;s|\.git$||')env -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls" \ --method POST \ --field title="feat(scope): WIP description" \ --field head="feat/my-branch" \ --field base="main" \ --field body="WIP — not ready for review" \ --field draft=true
# Convert draft to readyenv -u GITHUB_TOKEN gh api "repos/${SLUG}/pulls/${PR_NUM}" \ --method PATCH \ --field draft=false