Help us improve
Share bugs, ideas, or general feedback.
Merges a reviewed PR via squash or rebase, then cleans up branches and worktrees. Handles integration branches and multi-phase merge plans.
npx claudepluginhub nikhilsitaram/claude-caliper --plugin claude-caliper-workflowHow this skill is triggered — by the user, by Claude, or both
Slash command
/claude-caliper-workflow:pr-mergeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Merge (squash or rebase) and clean up branches and worktrees.
Merges open PRs in dependency order after CI/approval checks, closes linked issues from PRs or .claude-harness/features/active.json, deletes feature branches locally/remotely, prunes refs, and reports summary. Use for completing features, post-review merges, or release cycles.
Runs the 4-step pull request close sequence: reviewer comment, squash-merge, delete branch, close linked issues. Prevents half-done PR landings with dangling branches or open issues.
Resolves merged PR workflows to completed state: verifies merge status, collects metadata, transitions phases, and cleans up worktrees/branches.
Share bugs, ideas, or general feedback.
Merge (squash or rebase) and clean up branches and worktrees.
Prerequisite: A PR that has been reviewed (via /pr-review or manually).
Detect if CWD is inside a worktree:
[ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ]
If inside a worktree, note IN_WORKTREE=true and capture paths for cleanup:
MAIN_REPO="$(git worktree list --porcelain | head -1 | sed 's/^worktree //')"
WORKTREE_PATH="$(pwd)"
CWD_BRANCH=$(git rev-parse --abbrev-ref HEAD)
Stay in the worktree — gh pr merge is a GitHub API call that works from any directory.
Identify the PR from argument, current branch (gh pr view), or gh pr list --author @me --state open. If multiple candidates and you're not on a branch with an associated PR, ask the user to pick. Store PR number, branch name, and URL.
Detect environment:
DEFAULT_BRANCH from refs/remotes/origin/HEAD (fallback: main/master)IS_INTEGRATION — true when $BRANCH_NAME matches integrate/*; extract FEATURE=${BRANCH_NAME#integrate/}IS_INTEGRATION_CWD — true when $CWD_BRANCH matches integrate/* (CWD is an integration worktree, regardless of which PR is being merged)If branch protection requires human approval and the PR lacks it, tell the user and stop with the PR URL.
Pre-merge rebase check: Verify the PR branch is up-to-date with the base branch:
git fetch origin
git merge-base --is-ancestor origin/$DEFAULT_BRANCH HEAD
Use bare git fetch origin (no branch arg) so refs/remotes/origin/$DEFAULT_BRANCH actually advances. git fetch origin $DEFAULT_BRANCH only updates FETCH_HEAD — the is-ancestor check then compares against a stale ref and reports up-to-date when the branch is actually behind.
If behind (non-zero exit): rebase onto default branch, resolve conflicts, run tests, push with git push -u origin HEAD --force-with-lease. Comment on PR with conflict resolution details. Complex conflicts → stop and ask user.
Merge strategy:
IS_INTEGRATION=true): gh pr merge $PR_NUMBER --rebase — auto-detected, no flag neededintegrate/*): gh pr merge $PR_NUMBER --squash — auto-detected, no flag needed--rebase flag overrides for any non-auto-detected branchcaliper-settings get merge_strategy — use the returned value (squash or rebase) as the merge methodMulti-phase plans produce one squash commit per phase on the integration branch. Rebase preserves this per-phase history on main. Single-phase plans use squash (one phase = one commit). Phase PRs (base is integrate/*) always use --squash.
Never use --delete-branch — branch cleanup is handled in Step 3.
Capture the repo's auto-delete-on-merge setting once for use in branch deletion below:
AUTO_DELETE_REMOTE=$(gh api "repos/{owner}/{repo}" --jq .delete_branch_on_merge 2>/dev/null)
Local + remote branch deletion uses a gh-verified pattern. $B is a placeholder for the call site's branch name ($BRANCH_NAME, phase-a, etc.); $PR_REF is whatever uniquely identifies the PR — prefer $PR_NUMBER (the just-merged PR from Step 1) when available, since branch-name resolution returns the most recent PR for that name and could match a stale historical PR for reused names like phase-a:
state=$(gh pr view "${PR_REF:-$B}" --json state -q .state 2>/dev/null)
if [ "$state" = "MERGED" ]; then
git update-ref -d "refs/heads/$B" || echo "ERROR: $B is MERGED but update-ref failed"
if [ "$AUTO_DELETE_REMOTE" != "true" ]; then
git push origin --delete "$B" 2>/dev/null || echo "Note: remote $B already gone or protected"
fi
else
echo "Skipped $B (gh state: ${state:-unknown})"
fi
GitHub is the source of truth that the PR actually merged. The local delete is safer than git branch -D (which force-deletes regardless of merge state — squash-merged branches don't pass git branch -d's local merge check, so the gh state replaces git's local check). The remote delete fires only when the repo's auto-delete-on-merge setting is off, since otherwise GitHub already deleted it; git push origin --delete is gated by the same MERGED check and tolerates 404 (already-deleted) and 422 (branch protection) gracefully. Capture skips and errors in the Step 4 Summary so the user knows their cleanup partially no-op'd.
Worktree removal uses bare git worktree remove "$PATH" (no --force) — the PR has merged so the worktree should be clean. This stop-on-failure rule applies to every git worktree remove call in this section: if removal exits non-zero, the worktree has uncommitted/untracked content the user may want to keep — stop the cleanup chain, report the path, and let the user decide, since force-removal can destroy uncommitted or untracked work that may still be needed. Sibling phase worktrees (some may already be cleaned by earlier pr-merge runs) need an existence guard; the inner remove still propagates failure to the caller (the if block exits with the inner worktree remove exit code, so the orchestrator above sees non-zero and stops):
if git worktree list --porcelain | grep -q "^branch refs/heads/phase-X$"; then
git worktree remove .claude/worktrees/$FEATURE-phase-X
fi
Integration branch (IS_INTEGRATION=true):
IN_WORKTREE: call ExitWorktree with action: "remove" and discard_changes: true — the PR is already merged so local commits are safe to discard
cd "$MAIN_REPO" && git worktree remove "$WORKTREE_PATH", then prefix all subsequent commands with cd "$MAIN_REPO" &&phase-X)phase-X from plan.json, apply the pattern above$BRANCH_NAME (gh-verified)git worktree prune && git pull --rebase && git remote prune originStandard worktree (IN_WORKTREE=true):
IS_INTEGRATION_CWD=true: the orchestrator is invoking pr-merge from the integration worktree for a phase PR — do NOT remove the integration worktree. Just delete $BRANCH_NAME (gh-verified) and prune remotes (git remote prune origin). The orchestrator handles the integration worktree in Phase Wrap-Up step 7d/7e.IS_INTEGRATION_CWD=false (normal case, CWD branch matches PR branch):
ExitWorktree with action: "remove" and discard_changes: true — the PR is already merged so local commits are safe to discard
cd "$MAIN_REPO" && git worktree remove "$WORKTREE_PATH", then prefix all subsequent commands with cd "$MAIN_REPO" &&$BRANCH_NAME (gh-verified)git worktree prune && git pull --rebase && git remote prune originNo worktree: git checkout $DEFAULT_BRANCH && git pull --rebase && git remote prune origin, then delete $BRANCH_NAME (gh-verified).
Report: PR number/URL, merge status, cleanup status.
| Arg | Effect |
|---|---|
<PR number> | Target specific PR (/pr-merge 42) |
| (none) | Detect from current branch |
--rebase | Use rebase merge instead of squash (for multi-phase final PRs) |
| Mistake | Why |
|---|---|
Skipping ExitWorktree when it's available | cd doesn't persist across Bash tool calls — only ExitWorktree resets CWD at the session level. Always try ExitWorktree first; the cd "$MAIN_REPO" && fallback is for cross-session worktrees where ExitWorktree returns a no-op. |
| Deleting branch before removing worktree | Git refuses. Remove worktree first. |
Using --delete-branch on gh pr merge | Fails in worktree flows. Delete branch manually after. |
Preceded by: pr-review (or manual review)
Auto-invoked by: orchestrate — in pr-merge workflow mode