13 KiB
description, category, tools, model, version
| description | category | tools | model | version |
|---|---|---|---|---|
| Safely merge PR with verification and Linear integration | version-control-git | Bash(linearis *), Bash(git *), Bash(gh *), Read | inherit | 1.0.0 |
Merge Pull Request
Safely merges a PR after comprehensive verification, with Linear integration and automated cleanup.
Configuration
Read team configuration from .claude/config.json:
CONFIG_FILE=".claude/config.json"
TEAM_KEY=$(jq -r '.catalyst.linear.teamKey // "PROJ"' "$CONFIG_FILE")
TEST_CMD=$(jq -r '.catalyst.pr.testCommand // "make test"' "$CONFIG_FILE")
Process:
1. Identify PR to merge
If argument provided:
- Use that PR number:
/merge_pr 123
If no argument:
# Try current branch
gh pr view --json number,url,title,state,mergeable 2>/dev/null
If no PR on current branch:
gh pr list --limit 10 --json number,title,headRefName,state
Ask: "Which PR would you like to merge? (enter number)"
2. Get PR details
gh pr view $pr_number --json \
number,url,title,state,mergeable,mergeStateStatus,\
baseRefName,headRefName,reviewDecision
Extract:
- PR number, URL, title
- Mergeable status
- Base branch (usually main)
- Head branch (feature branch)
- Review decision (APPROVED, REVIEW_REQUIRED, etc.)
3. Verify PR is open and mergeable
state=$(gh pr view $pr_number --json state -q .state)
mergeable=$(gh pr view $pr_number --json mergeable -q .mergeable)
If PR not OPEN:
❌ PR #$pr_number is $state
Only open PRs can be merged.
If not mergeable (CONFLICTING):
❌ PR has merge conflicts
Resolve conflicts first:
gh pr checkout $pr_number
git fetch origin $base_branch
git merge origin/$base_branch
# ... resolve conflicts ...
git push
Exit with error.
4. Check if head branch is up-to-date with base
# Checkout PR branch
gh pr checkout $pr_number
# Fetch latest base
base_branch=$(gh pr view $pr_number --json baseRefName -q .baseRefName)
git fetch origin $base_branch
# Check if behind
if git log HEAD..origin/$base_branch --oneline | grep -q .; then
echo "Branch is behind $base_branch"
fi
If behind:
# Auto-rebase
git rebase origin/$base_branch
# Check for conflicts
if [ $? -ne 0 ]; then
echo "❌ Rebase conflicts"
git rebase --abort
exit 1
fi
# Push rebased branch
git push --force-with-lease
If conflicts during rebase:
❌ Rebase conflicts detected
Conflicting files:
$(git diff --name-only --diff-filter=U)
Resolve manually:
1. Fix conflicts in listed files
2. git add <resolved-files>
3. git rebase --continue
4. git push --force-with-lease
5. Run /catalyst-dev:merge_pr again
Exit with error.
5. Run local tests
Read test command from config:
test_cmd=$(jq -r '.catalyst.pr.testCommand // "make test"' .claude/config.json)
Execute tests:
echo "Running tests: $test_cmd"
if ! $test_cmd; then
echo "❌ Tests failed"
exit 1
fi
If tests fail:
❌ Local tests failed
Fix failing tests before merge:
$test_cmd
Or skip tests (not recommended):
/catalyst-dev:merge_pr $pr_number --skip-tests
Exit with error (unless --skip-tests flag provided).
6. Check CI/CD status
gh pr checks $pr_number
Parse output for failures:
- If all checks pass: continue
- If required checks fail: prompt user
- If optional checks fail: warn but allow
If required checks failing:
⚠️ Some required CI checks are failing
Failed checks:
- build (required)
- lint (required)
Passed checks:
- test ✅
- security ✅
Continue merge anyway? [y/N]:
If user says no: exit. If user says yes: continue (user override).
7. Check approval status
review_decision=$(gh pr view $pr_number --json reviewDecision -q .reviewDecision)
Review decisions:
APPROVED- proceedCHANGES_REQUESTED- prompt userREVIEW_REQUIRED- prompt usernull/ empty - no reviews, prompt user
If not approved:
⚠️ PR has not been approved
Review status: $review_decision
Continue merge anyway? [y/N]:
If user says no: exit. If user says yes: continue (user override).
Skip these prompts if requireApproval: false in config.
8. Extract ticket reference
branch=$(gh pr view $pr_number --json headRefName -q .headRefName)
title=$(gh pr view $pr_number --json title -q .title)
# From branch using configured team key
if [[ "$branch" =~ ($TEAM_KEY-[0-9]+) ]]; then
ticket="${BASH_REMATCH[1]}"
fi
# From title if not in branch
if [[ -z "$ticket" ]] && [[ "$title" =~ ($TEAM_KEY-[0-9]+) ]]; then
ticket="${BASH_REMATCH[1]}"
fi
9. Show merge summary
About to merge:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PR: #$pr_number - $title
From: $head_branch
To: $base_branch
Commits: $commit_count
Files: $file_count changed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Reviews: $review_status
CI: $ci_status
Tests: ✅ Passed locally
Ticket: $ticket (will move to Done)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Merge strategy: Squash and merge
Proceed? [Y/n]:
10. Execute squash merge
gh pr merge $pr_number --squash --delete-branch
Always:
- Squash merge (combines all commits into one)
- Delete remote branch automatically
Capture merge commit SHA:
merge_sha=$(git rev-parse HEAD)
11. Update Linear ticket
If ticket found and not using --no-update:
# Verify linearis is available
if ! command -v linearis &> /dev/null; then
echo "⚠️ Linearis CLI not found - skipping Linear ticket update"
echo "Install: npm install -g --install-links ryanrozich/linearis#feat/cycles-cli"
else
# Move to "Done"
linearis issues update "$ticket" --state "Done"
# Add merge comment
linearis comments create "$ticket" \
--body "✅ PR merged!\n\n**PR**: #${prNumber} - ${prTitle}\n**Merge commit**: ${mergeSha}\n**Merged into**: ${baseBranch}\n\nView PR: ${prUrl}"
fi
12. Delete local branch and update base
# Switch to base branch
git checkout $base_branch
# Pull latest (includes merge commit)
git pull origin $base_branch
# Delete local feature branch
git branch -d $head_branch
# Confirm deletion
echo "✅ Deleted local branch: $head_branch"
Always delete local branch - no prompt (remote already deleted).
13. Extract post-merge tasks
Read PR description:
desc_file="thoughts/shared/prs/${pr_number}_description.md"
if [ -f "$desc_file" ]; then
# Extract "Post-Merge Tasks" section
tasks=$(sed -n '/## Post-Merge Tasks/,/^##/p' "$desc_file" | grep -E '^\- \[')
fi
If tasks exist:
📋 Post-merge tasks from PR description:
- [ ] Update documentation
- [ ] Monitor error rates in production
- [ ] Notify stakeholders
Save these tasks? [Y/n]:
If yes:
# Save to thoughts
cat > "thoughts/shared/post_merge_tasks/${ticket}_tasks.md" <<EOF
# Post-Merge Tasks: $ticket
Merged: $(date)
PR: #$pr_number
$tasks
EOF
humanlayer thoughts sync
14. Report success summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ PR #$pr_number merged successfully!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Merge details:
Strategy: Squash and merge
Commit: $merge_sha
Base branch: $base_branch (updated)
Merged by: @$user
Cleanup:
Remote branch: $head_branch (deleted)
Local branch: $head_branch (deleted)
Linear:
Ticket: $ticket → Done ✅
Comment: Added with merge details
Post-merge tasks: $task_count saved to thoughts/
Next steps:
- Monitor deployment
- Check CI/CD pipeline
- Verify in production
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Flags
--skip-tests - Skip local test execution
/catalyst-dev:merge_pr 123 --skip-tests
--no-update - Don't update Linear ticket
/catalyst-dev:merge_pr 123 --no-update
--keep-branch - Don't delete local branch
/catalyst-dev:merge_pr 123 --keep-branch
Combined:
/catalyst-dev:merge_pr 123 --skip-tests --no-update
Error Handling
Rebase conflicts:
❌ Rebase conflicts detected
Conflicting files:
- src/app.ts
- tests/app.test.ts
Resolve manually:
gh pr checkout $pr_number
git fetch origin $base_branch
git rebase origin/$base_branch
# Fix conflicts
git add <files>
git rebase --continue
git push --force-with-lease
/catalyst-dev:merge_pr $pr_number
Tests failing:
❌ Tests failed (exit code 1)
Failed tests:
- validation.test.ts:45 - Expected true but got false
- auth.test.ts:12 - Timeout exceeded
Fix tests or skip (not recommended):
/catalyst-dev:merge_pr $pr_number --skip-tests
CI checks failing:
⚠️ Required CI checks failing
Failed:
- build: Compilation error in src/types.ts
- security: Dependency vulnerability found
You can:
1. Fix issues and try again
2. Override and merge anyway (not recommended)
Override? [y/N]:
Linearis CLI not found:
⚠️ Linearis CLI not found
PR merged successfully, but Linear ticket not updated.
Install Linearis:
npm install -g --install-links ryanrozich/linearis#feat/cycles-cli
Configure:
export LINEAR_API_TOKEN=your_token
Then update ticket manually:
linearis issues update $ticket --state "Done"
Linear API error:
⚠️ Could not update Linear ticket $ticket
Error: Ticket not found or API unavailable
PR merged successfully, but ticket status not updated.
Update manually in Linear.
Branch deletion error:
⚠️ Could not delete local branch $head_branch
Error: Branch has unpushed commits
This won't affect the merge (already complete).
Delete manually: git branch -D $head_branch
Configuration
Uses .claude/config.json:
{
"catalyst": {
"project": {
"ticketPrefix": "RCW"
},
"linear": {
"teamKey": "RCW",
"doneStatusName": "Done"
},
"pr": {
"defaultMergeStrategy": "squash",
"deleteRemoteBranch": true,
"deleteLocalBranch": true,
"updateLinearOnMerge": true,
"requireApproval": false,
"requireCI": false,
"testCommand": "make test"
}
}
}
Examples
Happy path (all checks pass):
/catalyst-dev:merge_pr 123
Running tests: make test
✅ All tests passed
✅ CI checks passed
✅ PR approved
About to merge PR #123...
[shows summary]
Proceed? Y
✅ Merged!
✅ Linear ticket RCW-13 → Done
✅ Branches deleted
With failing CI (user override):
/catalyst-dev:merge_pr 124
⚠️ Some CI checks failing
Continue anyway? y
✅ Merged (with overrides)
Skip tests:
/catalyst-dev:merge_pr 125 --skip-tests
⚠️ Skipping tests (not recommended)
✅ Merged!
Linearis not installed:
/catalyst-dev:merge_pr 126
✅ PR merged successfully!
⚠️ Linearis CLI not found - Linear ticket not updated
Install Linearis to enable automatic ticket updates.
Safety Features
Fail fast on:
- Merge conflicts (can't auto-resolve)
- Test failures (unless --skip-tests)
- Rebase conflicts
- PR not in mergeable state
Prompt for confirmation on:
- Missing required approvals
- Failing CI checks
- Any exceptional circumstance
Always automated:
- Rebase if behind (no conflicts)
- Squash merge
- Delete remote branch
- Delete local branch
- Update Linear to Done (if Linearis available)
- Pull latest base branch
Graceful degradation:
- If Linearis not installed, warn but continue
- Merge succeeds regardless of Linear integration
Post-Merge Workflow
PR merged
↓
Linear ticket → Done (if Linearis available)
↓
Branches deleted
↓
Base branch updated locally
↓
Post-merge tasks extracted
↓
Monitor deployment
Remember:
- Always squash merge - clean history
- Always delete branches - no orphan branches
- Always run tests - unless explicitly skipped
- Auto-rebase - keep up-to-date with base
- Fail fast - stop on conflicts or test failures
- Update Linear - move ticket to Done automatically (if Linearis available)
- Extract tasks - save post-merge checklist
- Clear summary - show what happened
- Only prompt for exceptions - approvals missing, CI failing
- Graceful degradation - Work without Linearis if needed