Enforce project hygiene throughout the PR lifecycle. Use before creating PRs to validate worktrees, branch naming, and issue linking. Use when creating PRs to ensure proper "Fixes
Enforce project hygiene throughout the pull request lifecycle. This skill provides checklists, validation commands, and templates to ensure PRs properly close issues and follow project conventions.
IMPORTANT: You are operating in a stateless session. Each Claude Code session is isolated.
You CANNOT:
You MUST NOT say things like:
When addressing review comments, choose ONE of these options:
When: Change is small-ish and worth doing. Action: Implement fix, commit, reply with: "Fixed in commit {sha}: {description}"
When: Change is large AND worth doing AND not critical to PR. Action:
gh issue creategh sub-issue addWhen: Suggestion is nitpicky, negligible, or you disagree. Action: Reply explaining why not addressing. Be professional. NEVER use for: Test failures, errors, security concerns.
Project Hygiene Invariant: Each PR should:
.claude/skills/git-worktrees skill (you should not be in /workspace, which should always be kept up to date with origin main - instead you should be in wip/<feature branch>)m<milestone_id>-p<parent_issue_id>-i<issue_num>-<slug> (with appropriate variations)Why This Matters: PRs #221 and #222 didn't include "Fixes #XYZ" keywords, causing issues to remain open after merge. This skill prevents such "orphan PRs."
When working in a tmux session, window names reflect the current PR lifecycle phase for quick visual reference.
Format: <emoji><prefix><number>
Prefix Conventions:
i = Issue number (e.g., ๐ปi483 = coding on issue #483)pr = Pull request number (e.g., ๐งpr485 = maintaining PR #485)Examples:
๐ปi483 - Coding on issue #483๐i483 - Submitting PR for issue #483๐งpr485 - Maintaining PR #485โณpr485 - Waiting for tests on PR #485Phase Emojis:
| Phase | Emoji | When Used | Format Example | Command |
|---|---|---|---|---|
| 1. Gathering information | ๐ | Research/discovery | ๐i483 |
Manual |
| 2. Coding | ๐ป | Active development | ๐ปi483 |
/gh-start-issue |
| 3. Waiting for tests | โณ | CI/CD checks running | โณpr485 |
/pr-workflow |
| 4. Waiting for user | โ | Needs input/clarification | โi483 or โpr485 |
Manual |
| 5. Submitting PR | ๐ | Creating/pushing PR | ๐i483 |
/pr-workflow |
| 6. PR maintenance | ๐ง | Addressing feedback/fixes | ๐งpr485 |
/pr-workflow |
| 7. Cleanup | ๐งน | Post-merge cleanup | ๐งนpr485 |
Manual |
Automatic Updates:
/gh-start-issue sets tmux to ๐ปi<issue_num> (coding phase on issue)/pr-workflow updates tmux through phases:๐i<issue_num> โ (PR created) โ ๐งpr<pr_num> โ โณpr<pr_num> โ ๐งpr<pr_num>Manual Updates:
# Update tmux window name manually for issue work
ISSUE_NUM=<your_issue_number>
tmux rename-window "๐i${ISSUE_NUM}" # Research phase on issue
# Update tmux window name manually for PR work
PR_NUM=<your_pr_number>
tmux rename-window "๐งpr${PR_NUM}" # PR maintenance
Why This Convention:
.devcontainer/.tmux.conf)Run these validations before creating a PR:
# 1. Verify you're in a worktree (not main)
pwd | grep -q "wip/" && echo "In worktree" || echo "WARNING: Not in wip/ worktree"
# 2. Check branch name follows convention
BRANCH=$(git branch --show-current)
echo "Current branch: $BRANCH"
# Should match: m<id>-p<id>-i<num>-<slug> or variants (p<id>-i<num>-<slug>, i<num>-<slug>)
# 3. Extract issue number from branch (look for i<num> pattern)
ISSUE_NUM=$(echo "$BRANCH" | grep -oP 'i\K[0-9]+' || echo "$BRANCH" | grep -oE '[0-9]+' | head -1)
echo "Issue number: $ISSUE_NUM"
# 4. Verify issue exists and is open
gh issue view "$ISSUE_NUM" --json state,title
# 5. Check commits for "Fixes #" keyword
git log origin/main..HEAD --oneline | grep -i "fixes #\|closes #\|resolves #" || echo "WARNING: No 'Fixes #' keyword found in commits"
Why: Feature work should happen in worktrees, not the main worktree.
# List all worktrees
git worktree list
# Verify current directory is in wip/
pwd | grep -q "wip/" && echo "In worktree" || echo "WARNING: Not in wip/ worktree"
Expected: You should be in a wip/<branch-name>/ directory.
Format variations:
m<milestone_id>-p<parent_id>-i<issue_num>-<issue_slug>p<parent_id>-i<issue_num>-<issue_slug>m<milestone_id>-i<issue_num>-<issue_slug>i<issue_num>-<issue_slug>Examples:
m8-p123-i234-add-authenticationp123-i234-add-authenticationi42-add-authenticationValidation:
BRANCH=$(git branch --show-current)
# Check format: must contain i<number> pattern
if [[ "$BRANCH" =~ i[0-9]+ ]]; then
echo "Branch name follows convention"
else
echo "WARNING: Branch name '$BRANCH' doesn't follow convention (missing i<number>)"
fi
Check issue exists and is open:
ISSUE_NUM=$(git branch --show-current | grep -oE '[0-9]+' | head -1)
# View issue details
gh issue view "$ISSUE_NUM" --json number,title,state,labels
# Verify it's open
STATE=$(gh issue view "$ISSUE_NUM" --json state -q '.state')
if [ "$STATE" = "OPEN" ]; then
echo "Issue #$ISSUE_NUM is open"
else
echo "WARNING: Issue #$ISSUE_NUM is $STATE"
fi
Verify commits reference the issue:
# Check for GitHub closing keywords in commits
git log origin/main..HEAD --pretty=format:"%s" | \
grep -iE "(fix(es)?|close[sd]?|resolve[sd]?)\s*#\d+" || \
echo "WARNING: No GitHub closing keywords found in commit messages"
GitHub closing keywords: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved
Format: <emoji> <type>[scope]: <description>
Common Types:
| Type | Emoji | Use For |
|---|---|---|
| feat | โจ | New features |
| fix | ๐ฉน | Bug fixes |
| docs | ๐ | Documentation |
| refactor | โป๏ธ | Code refactoring |
| test | ๐งช | Tests |
| build | ๐ง | Build system |
| perf | โก๏ธ | Performance |
| release | ๐ | Releases |
Examples:
โจ feat(cli): add deployment management commands๐ฉน fix: resolve race condition in thread handler๐ docs: add PR lifecycle skillAlways include "Fixes #XYZ" to auto-close the issue:
## Summary
<1-3 bullet points describing the changes>
## Changes
- <specific change 1>
- <specific change 2>
## Related Issues
Fixes #<issue_number>
## Test Plan
- [ ] <test item 1>
- [ ] <test item 2>
---
Generated with [Claude Code](https://claude.com/claude-code)
If the issue that the PR fixes is part of a milestone, add the PR to that milestone.
Using gh CLI with proper body:
ISSUE_NUM=$(git branch --show-current | grep -oE '[0-9]+' | head -1)
gh pr create \
--title "โจ feat: <description>" \
--body "$(cat <<EOF
## Summary
- <summary point>
## Changes
- <change 1>
- <change 2>
## Related Issues
Fixes #$ISSUE_NUM
## Test Plan
- [ ] <test item>
---
Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
After creating PR, verify the link:
# Get PR number
PR_NUM=$(gh pr view --json number -q '.number')
# Check closing issues
gh pr view "$PR_NUM" --json closingIssuesReferences -q '.closingIssuesReferences[].number'
# Should output the issue number
Copilot and other automated reviewers may add comments. Monitor and address them:
PR_NUM=$(gh pr view --json number -q '.number')
# Get owner/repo dynamically from current repository
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# Check for Copilot comments (use --paginate to get ALL comments)
gh api repos/$REPO/pulls/$PR_NUM/comments --paginate \
--jq '.[] | select(.user.login == "copilot") | {id, body, path, line}'
# Check for all review comments (use --paginate to get ALL comments)
gh api repos/$REPO/pulls/$PR_NUM/comments --paginate \
--jq '.[] | {id, user: .user.login, body: .body[0:100]}'
After addressing feedback, reply with commit reference:
Note: Do not at-mention copilot in your reply, otherwise copilot will try to implement something.
PR_NUM=$(gh pr view --json number -q '.number')
COMMENT_ID=<comment_id>
COMMIT_SHA=$(git rev-parse --short HEAD)
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
gh api repos/$REPO/pulls/$PR_NUM/comments/$COMMENT_ID/replies \
-f body="Fixed in commit $COMMIT_SHA: <brief description>"
PR_NUM=$(gh pr view --json number -q '.number')
# View PR checks
gh pr checks "$PR_NUM"
# View PR status
gh pr view "$PR_NUM" --json state,mergeable,reviewDecision
Check if the issue was auto-closed:
ISSUE_NUM=<issue_number>
# Check issue state
STATE=$(gh issue view "$ISSUE_NUM" --json state -q '.state')
echo "Issue #$ISSUE_NUM state: $STATE"
if [ "$STATE" = "CLOSED" ]; then
echo "Issue #$ISSUE_NUM was auto-closed"
else
echo "WARNING: Issue #$ISSUE_NUM is still OPEN"
echo "Manually close with: gh issue close $ISSUE_NUM"
fi
If the issue wasn't auto-closed:
Ask the user first before doing this:
ISSUE_NUM=<issue_number>
PR_NUM=<pr_number>
# Close with reference to PR
gh issue close "$ISSUE_NUM" --comment "Closed via PR #$PR_NUM"
After PR merge, clean up the worktree:
# Switch to main worktree first
cd /workspace
# Remove the worktree
WORKTREE_PATH="wip/<branch-name>" # e.g., wip/i42-add-auth or wip/m8-p123-i234-add-auth
git worktree remove "$WORKTREE_PATH"
# Prune stale references
git worktree prune --verbose
Delete local and remote branches:
BRANCH="<branch_name>" # e.g., i42-add-auth or m8-p123-i234-add-auth
# Delete local branch
git branch -d "$BRANCH"
# If not fully merged, force delete
# git branch -D "$BRANCH"
# Delete remote branch (GitHub usually does this automatically)
git push origin --delete "$BRANCH" 2>/dev/null || echo "Remote branch already deleted"
/workspace should be kept up to date with origin maincd /workspace then ensure workspace is refreshed from origin main/workspace (instead of wip/<feature branch>) that would be affected by this action, ask user what to do# Variables
ISSUE_NUM=225
BRANCH="claude/225-pr-lifecycle-skill"
WORKTREE_PATH="wip/claude-225-pr-lifecycle-skill"
# 1. Verify issue closed
gh issue view "$ISSUE_NUM" --json state -q '.state'
# 2. Switch to main worktree
cd /workspace
# 3. Pull latest changes
git checkout main
git pull origin main
# 4. Remove worktree
git worktree remove "$WORKTREE_PATH"
# 5. Delete local branch
git branch -d "$BRANCH"
# 6. Prune
git worktree prune --verbose
# 7. Verify cleanup - should only show main worktree
git worktree list
# Show only non-main branches (should be empty after cleanup)
git branch | grep -v "^\*" | grep -v "main\|master"
| Check | Command | Expected |
|---|---|---|
| In worktree | pwd | grep wip/ |
In wip/ directory |
| Branch format | git branch --show-current |
user/num-slug |
| Issue open | gh issue view N --json state |
OPEN |
| Has "Fixes #" | git log | grep -i "fixes #" |
Found keyword |
Any of these in PR body will auto-close the linked issue:
close, closes, closedfix, fixes, fixedresolve, resolves, resolvedFormat: Fixes #123 or Fixes owner/repo#123
| Emoji | Type | Triggers |
|---|---|---|
| โจ | feat | MINOR bump |
| ๐ฉน | fix | PATCH bump |
| ๐จ | BREAKING | MAJOR bump |
| ๐ | docs | No bump |
| โป๏ธ | refactor | No bump |
| ๐งช | test | No bump |
| ๐ง | build | No bump |
| ๐ | release | N/A |
# Get owner/repo for API calls
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
# View issue
gh issue view <num> --json number,title,state
# View PR
gh pr view --json number,title,state,closingIssuesReferences
# PR comments (for reviews) - use --paginate to get ALL comments
gh api repos/$REPO/pulls/<num>/comments --paginate
# Reply to comment
gh api repos/$REPO/pulls/<num>/comments/<id>/replies -f body="message"
# Close issue manually
gh issue close <num> --comment "Closed via PR #N"
git-worktrees SkillUse git-worktrees skill to create proper worktrees before starting work:
# Create worktree for new issue
git worktree add -b alice/42-new-feature wip/alice-42-new-feature main
gh-sub-issue SkillFor issues with parent-child relationships, verify PR targets:
# Check if issue has parent
gh sub-issue list 42 --relation parent
# If parent exists, PR should target parent branch, not main
Cause: Missing "Fixes #N" keyword in PR body.
Solution:
# Close manually
gh issue close <num> --comment "Closed via PR #<pr_num>"
# Prevent future issues: always include "Fixes #N" in PR body
Cause: Branch created without following convention.
Solution: Rename branch before PR:
git branch -m old-name i42-proper-name
git push origin -u i42-proper-name
git push origin --delete old-name
Cause: Branch name doesn't contain issue number.
Solution: Check branch name, rename if needed:
git branch --show-current
# If no number, rename branch to include issue number
Cause: Wrong issue number in PR body.
Solution: Edit PR body on GitHub or:
gh pr edit <num> --body "$(cat updated-body.md)"
@docs/dev/github-workflow.md@docs/dev/git-scm-conventions.md