Merge Pull Request
Prepare PR for human merge via GitHub web UI (<30 seconds prep).
Purpose
Final step in V3 workflow: commit → create PR → fix PR → merge PR
Philosophy: AI prepares, human executes merge (trust + safety)
Handles:
- Verify CI passing (all checks green)
- Verify approvals (recommended but not required)
- Check working tree clean
- Verify Beads issue already closed (NOT close it - that happens at PR creation)
- Provide merge instructions for web UI
- Post-merge cleanup (switch to master, sync Beads, cache docs)
Human responsibility:
- Execute merge via GitHub web UI
- Delete remote branch (via web UI)
When to Use This Skill
Trigger phrases:
- "merge the PR"
- "merge it"
- "merge this"
- "prepare to merge"
- "ready to merge"
Automatically invoked when:
- User asks to merge current branch's PR
- User says "merge" in context of an open PR
Workflow
1. Get PR Context
# Get current branch
BRANCH=$(git branch --show-current)
# Extract FEATURE_KEY from feature-<KEY>
FEATURE_KEY=$(echo $BRANCH | sed 's/^feature-//')
# Get PR number and URL
gh pr view --json number,url -q '{number, url}'
2. Verify PR Status
A. Check CI Status
gh pr view --json statusCheckRollup -q '.statusCheckRollup[] | select(.status == "COMPLETED") | {name, conclusion}'
Criteria:
- ✅ All required checks passed → Proceed
- ⚠️ Some checks pending → Warn user, let them decide
- ❌ Failing checks → Block prep, suggest "fix the PR" first
B. Check Approvals (Recommended)
gh pr view --json reviewDecision -q '.reviewDecision'
Criteria:
- ✅ APPROVED → Ideal, proceed
- ⚠️ Empty (no reviews) → Warn but allow (solo dev OK)
- ❌ CHANGES_REQUESTED → Block prep, suggest addressing feedback first
C. Check Merge Conflicts
gh pr view --json mergeable -q '.mergeable'
Criteria:
- ✅ MERGEABLE → Proceed
- ❌ CONFLICTING → Block prep, suggest rebasing first
3. Check Working Tree
CRITICAL: Must be clean before closing Beads
git status --porcelain
If dirty:
- Ask user: "Working tree has uncommitted changes. Commit them first?"
- Options:
- a) Commit changes now (with Feature-Key trailer)
- b) Stash changes
- c) Abort merge prep
If clean:
- Proceed to Beads verification
4. Verify Beads Issue Already Closed
CRITICAL: Issue must be closed BEFORE merge (happens at PR creation)
# Get current issue status
STATUS=$(bd show $FEATURE_KEY --json | jq -r '.status')
if [ "$STATUS" != "closed" ]; then
echo "❌ Error: Beads issue must be closed BEFORE merge"
echo ""
echo "Current status: $STATUS"
echo "Expected: closed"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Why this matters:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Issues should be closed at PR creation time, not merge time."
echo "This ensures JSONL merges atomically with code (no post-merge mutations)."
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Recovery options:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Option 1: Close issue now (less ideal but works)"
echo " bd close $FEATURE_KEY --reason \"Closing before merge in PR #$PR_NUMBER\""
echo " bd sync && git push"
echo " # Then retry merge"
echo ""
echo "Option 2: Close and recreate PR (atomic pattern)"
echo " bd close $FEATURE_KEY --reason \"Work complete, ready for review\""
echo " bd sync && git push"
echo " gh pr close $PR_NUMBER"
echo " gh pr create # Creates new PR with JSONL already closed"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Future prevention:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "When creating PRs, use create-pull-request skill which asks:"
echo " 'Is work complete?' → YES closes issue BEFORE creating PR"
echo ""
exit 1
fi
echo "✅ Beads issue already closed (status: $STATUS)"
echo " → JSONL will merge atomically with code"
echo ""
Why verify instead of close:
- Structural fix: Issues closed at PR creation (create-pull-request skill)
- Atomic merge: JSONL already on feature branch when PR created
- No post-merge mutations: Never need to modify JSONL on master
- Prevents hook conflicts: All Beads state changes on feature branch
- Clear workflow: Closed = "ready to ship", Open = "work in progress"
5. Present Merge Instructions to User (MANDATORY)
This confirmation is REQUIRED. Provide web UI link and clear instructions.
✅ PR #156 Ready to Merge
Feature: GUARD_SKILL_ACTIVATION
Branch: feature-GUARD_SKILL_ACTIVATION
Beads: bd-pso (closed ✅)
Status:
✅ CI: All checks passed
✅ Beads: Closed and committed to feature branch
✅ Conflicts: None
✅ Working tree: Clean
📝 Merge Instructions:
1. Open PR in GitHub web UI:
https://github.com/fengning-starsend/prime-radiant-ai/pull/156
2. Click "Squash and merge" button
3. Verify squash commit message includes:
- Feature title
- PR number
- All commits squashed
4. Check "Delete branch" checkbox ✅
5. Confirm merge
After you merge, say "cleanup" and I'll switch to master and pull latest.
Why human merges:
- GitHub web UI handles squash reliably
- User sees final commit message before merge
- No risk of
gh pr mergelocal cleanup failures - User controls exact timing of merge
- Web UI provides visual confirmation
6. Wait for Human to Merge
Do not proceed automatically. Wait for:
- User confirmation: "merged" / "done" / "cleanup"
- User explicitly asks to verify merge succeeded
7. Post-Merge Cleanup (After User Confirms)
# Verify PR actually merged
STATE=$(gh pr view $PR_NUMBER --json state -q .state)
if [ "$STATE" != "MERGED" ]; then
echo "⚠️ PR not merged yet. Waiting for merge..."
exit 1
fi
# Get merge commit hash
MERGE_COMMIT=$(gh pr view $PR_NUMBER --json mergeCommit -q '.mergeCommit.oid' | cut -c1-7)
# Switch to master
git checkout master
# Pull latest (includes merge)
git pull origin master
# Verify merge commit is on master (search recent 20 commits, not just HEAD)
git log --oneline -20 | grep -q "$MERGE_COMMIT"
# Delete local feature branch (remote already deleted via web UI)
git branch -d feature-$FEATURE_KEY 2>/dev/null || echo "Branch already deleted"
# Sync Beads (imports closure from git)
bd sync
7.5. Auto-Cache Docs to Serena (If Docs Exist)
After successful merge, cache docs for searchability:
DOC_DIR="docs/$FEATURE_KEY"
if [ -d "$DOC_DIR" ]; then
echo "💾 Caching docs to Serena..."
# Get issue details
ISSUE_JSON=$(bd show $FEATURE_KEY --json)
ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
ISSUE_TYPE=$(echo "$ISSUE_JSON" | jq -r '.type')
# Combine all markdown files into cache
CACHE_CONTENT="# $FEATURE_KEY: $ISSUE_TITLE
**Status:** closed (merged in PR #$PR_NUMBER)
**Type:** $ISSUE_TYPE
**Merged:** $(date +%Y-%m-%d)
**Source:** $DOC_DIR/
**[CACHE]** This is a searchable cache. Source of truth: git ($DOC_DIR/)
---
"
# Append all markdown files
find "$DOC_DIR" -name "*.md" -type f | while read file; do
echo "## $(basename $file .md)" >> /tmp/cache_${FEATURE_KEY}.tmp
echo "" >> /tmp/cache_${FEATURE_KEY}.tmp
cat "$file" >> /tmp/cache_${FEATURE_KEY}.tmp
echo "" >> /tmp/cache_${FEATURE_KEY}.tmp
echo "---" >> /tmp/cache_${FEATURE_KEY}.tmp
echo "" >> /tmp/cache_${FEATURE_KEY}.tmp
done
CACHE_CONTENT+=$(cat /tmp/cache_${FEATURE_KEY}.tmp)
rm /tmp/cache_${FEATURE_KEY}.tmp
# Write to Serena using MCP tool
mcp__serena__write_memory(
memory_file_name="${FEATURE_KEY}_merged",
content="$CACHE_CONTENT"
)
echo "✅ Cached to Serena: ${FEATURE_KEY}_merged"
echo " Search with: /search '@serena ${FEATURE_KEY}'"
else
echo "ℹ️ No docs to cache (Beads only)"
fi
Why auto-cache:
- Merged work becomes searchable via Serena
- Clearly marked as [CACHE] (not source of truth)
- Enables fast cross-feature references
- No manual step required
- Only happens after successful merge
Verification:
- ✅ On master branch
- ✅ Latest merge pulled
- ✅ Local feature branch deleted
- ✅ Beads synced
- ✅ Docs cached to Serena (if exist)
8. Confirm Completion
✅ Merge Complete
Merged: GUARD_SKILL_ACTIVATION → master
Commit: a1b2c3d (squashed 50 commits)
Beads: bd-pso (closed, synced to master)
Workspace:
✅ On master branch
✅ Pulled latest changes
✅ Local branch deleted
✅ Beads state synced
Ready for next feature.
Integration Points
With Beads
- Verifies issue already closed (closed at PR creation time by create-pull-request skill)
- JSONL already in PR (merged atomically with code)
- Syncs after merge (imports from master)
- No post-merge mutations (prevents hook conflicts on master)
With create-pull-request
- Completes lifecycle: create-pull-request → fix-pr-feedback → merge-pr
- Same PR (no new PRs)
- Bidirectional linking (PR → Beads, Beads → PR)
With GitHub Web UI
- Human control (user decides when to merge)
- Visual confirmation (see commit message before merging)
- Reliable squash (GitHub handles it)
- Branch cleanup (delete via checkbox)
Safety Guardrails
Before preparing:
- ✅ Verify CI passing (block if failing)
- ✅ Verify no conflicts (block if conflicting)
- ⚠️ Check approvals (warn if missing, allow for solo dev)
- ✅ Verify working tree clean (block if dirty)
Auto-prep allowed:
- All checks passed
- No conflicts
- Working tree clean
Require user approval:
- Checks pending (ask if they want to wait)
- No approvals yet (warn but allow)
- CHANGES_REQUESTED (block, suggest fix-pr-feedback)
Never auto-prep:
- Failing CI checks
- Merge conflicts
- Dirty working tree
- User hasn't confirmed
Best Practices
- Verify issue already closed - Should be closed at PR creation time
- Clean working tree - No uncommitted changes before verification
- Human merges - Trust web UI reliability
- Wait for confirmation - Don't assume merge happened
- Verify merge - Check PR state before cleanup
- Sync Beads after - Import closure from master
- Atomic merge pattern - JSONL merges with code (no post-merge mutations)
What This Skill Does
✅ Verify PR merge-readiness (CI, approvals, conflicts) ✅ Check working tree clean (block if dirty) ✅ Verify Beads issue already closed (error if not) ✅ Provide merge instructions with web UI link ✅ Wait for human to merge ✅ Clean up local workspace after merge ✅ Sync Beads state from master ✅ Auto-cache docs to Serena (if exist)
What This Skill DOESN'T Do
❌ Merge PR automatically (human does via web UI) ❌ Delete remote branch (human does via web UI checkbox) ❌ Skip user confirmation (always waits for "merged" confirmation) ❌ Close Beads issue (already closed at PR creation time by create-pull-request skill) ❌ Push to master (never, pre-push hook blocks) ❌ Modify JSONL on master (never - atomic merge pattern prevents this)
Anti-Patterns
❌ Closing Beads at merge time (should be closed at PR creation) ❌ Committing to master directly (pre-push hook blocks this) ❌ Auto-merging without human confirmation ❌ Skipping working tree check ❌ Not verifying merge succeeded before cleanup ❌ Modifying JSONL on master (causes hook conflicts)
Example Scenarios
Scenario 1: Clean Merge (Issue Already Closed)
User: "merge the PR"
AI:
1. Checks PR #156: CI passed ✅, No conflicts ✅
2. Checks working tree: Clean ✅
3. Verifies bd-pso status: closed ✅ (closed at PR creation)
4. Presents merge instructions with web UI link
5. Waits...
User: [Merges via web UI, deletes branch]
User: "merged"
AI:
6. Verifies PR state = MERGED
7. Switches to master, pulls
8. Deletes local branch
9. Syncs Beads
10. Caches docs to Serena
11. Confirms: "✅ Merge complete, workspace clean"
Scenario 2: Dirty Working Tree
User: "merge the PR"
AI:
1. Checks PR status: ✅ Ready
2. Checks working tree: ❌ Dirty (.beads/beads.left.meta.json modified)
3. Asks: "Working tree has uncommitted changes. Options:
a) Commit changes now
b) Stash changes
c) Abort merge prep"
User: "commit them"
AI:
4. Commits changes with Feature-Key trailer
5. Pushes to feature branch
6. Proceeds with Beads verification
7. Verifies issue closed ✅
8. Presents merge instructions
Scenario 3: Failing CI
User: "merge the PR"
AI:
1. Checks CI: ❌ 2 checks failing (lint, test)
2. Blocks merge prep
3. Suggests: "❌ Cannot prepare merge: CI checks failing
Failing checks:
- lint: Line too long
- test: AssertionError in test_foo
Suggestion: Use 'fix the PR' to address failures first."
Scenario 4: Issue Not Closed (Structural Problem)
User: "merge the PR"
AI:
1. Checks PR #200: CI passed ✅, No conflicts ✅
2. Checks working tree: Clean ✅
3. Checks Beads status: ❌ in_progress (NOT closed!)
4. Errors with recovery options:
"❌ Error: Beads issue must be closed BEFORE merge
Current status: in_progress
Expected: closed
Why this matters:
Issues should be closed at PR creation time, not merge time.
This ensures JSONL merges atomically with code.
Recovery options:
Option 1: Close issue now (less ideal but works)
bd close bd-xyz --reason 'Closing before merge in PR #200'
bd sync && git push
# Then retry merge
Option 2: Close and recreate PR (atomic pattern)
bd close bd-xyz --reason 'Work complete, ready for review'
bd sync && git push
gh pr close 200
gh pr create # Creates new PR with JSONL already closed
Future prevention:
When creating PRs, use create-pull-request skill which asks:
'Is work complete?' → YES closes issue BEFORE creating PR"
Scenario 5: User Merges Before Confirmation
User: "merge the PR"
AI:
1. Prepares merge (closes Beads, commits, pushes)
2. Presents merge instructions
3. Waits for confirmation...
User: [Merges immediately without saying anything]
User: "check if it merged"
AI:
4. Checks PR state: MERGED ✅
5. Proceeds with cleanup
6. Confirms completion
Troubleshooting
"Working tree dirty" error
Cause: Uncommitted changes in working tree Fix: Commit or stash changes, then retry
"PR not found" error
Cause: Not on a feature branch, or PR doesn't exist Fix: Switch to feature branch with open PR
"CI checks failing" error
Cause: Required checks not passing Fix: Use fix-pr-feedback skill to address failures
"Merge conflicts" error
Cause: Branch conflicts with master
Fix: Rebase on master: git fetch origin master && git rebase origin/master
"bd close failed" error
Cause: Beads issue doesn't exist or already closed Fix: Verify issue ID matches branch name
Future Enhancements
- Auto-wait for CI - Poll until checks complete
- Conflict auto-resolution - Attempt automatic rebase
- Smart approval - Request review if needed
- Post-merge validation - Verify deployment succeeded
- Rollback support - Quick revert if issues found
Last Updated: 2025-11-13 Related Skills: create-pull-request, fix-pr-feedback, sync-feature-branch Helper Scripts: scripts/bd-link-pr References:
- PR workflow: AGENTS.md
- Beads integration: .claude/skills/beads-workflow/SKILL.md
Changelog:
- 2025-11-13: Initial creation with CI verification, approval checking, mandatory user confirmation
- 2025-11-13: BREAKING - Changed to human-merge workflow (Option A). AI prepares (closes Beads on feature branch), human merges via web UI, AI cleans up after confirmation. Fixes gh pr merge local cleanup failures.