|
| 1 | +--- |
| 2 | +name: stack-maintenance |
| 3 | +description: Maintains stacked GitHub PRs and branches. Triggers on "fix stacked PRs", "merge this PR stack", "restack branches", "turn on auto-merge", `gh stack`, `gh pr`, `jj spr`, wrong PR bases, merge queues, or force-pushing stack repairs. |
| 4 | +--- |
| 5 | + |
| 6 | +# Stack Maintenance |
| 7 | + |
| 8 | +Keep stacked GitHub pull requests boring: map the real stack, act from the bottom, verify after every remote action, and rewrite branches only with a lease. |
| 9 | + |
| 10 | +Use this skill for PR chains, dependent branches, branch retargeting, merge queues, stale parent branches, and review/merge triage. Prefer existing stack tools (`gh stack`, `gh pr stack`, `jj spr`) when the repository has them; fall back to plain `gh` and `git` only after confirming the tool is unavailable or insufficient. |
| 11 | + |
| 12 | +## Rules From Prior Stacked PR Incidents |
| 13 | + |
| 14 | +- Trust fresh GitHub metadata, not memory. Fetch `baseRefName`, `headRefName`, `mergeStateStatus`, `mergeable`, `reviewDecision`, checks, and auto-merge state before acting. |
| 15 | +- Stacked PRs usually merge bottom-up. A child can become conflicted after its parent merges; expect to rebase or retarget it before merging. |
| 16 | +- `gh pr merge --auto --squash` can merge immediately for intermediate stack branches. Re-read the PR afterward; do not infer from command text. |
| 17 | +- Main may use a merge queue. If GitHub says the merge strategy is controlled by the queue, retry without an explicit strategy, then re-read `state`, `mergedAt`, and `mergeCommit`. |
| 18 | +- Do not approve a PR before reviewing its diff and running the relevant local check. "Clean" only means mergeable, not reviewed. |
| 19 | +- Use `--delete-branch=false` for stack branches unless the user explicitly asks to prune. Other PRs may still depend on them. |
| 20 | +- Use `git push --force-with-lease`, never plain force, after rebasing a PR branch. |
| 21 | +- Stop before rewriting someone else's branch unless ownership and permission are clear. |
| 22 | + |
| 23 | +## Map The Stack First |
| 24 | + |
| 25 | +Start with the current checkout and remote state: |
| 26 | + |
| 27 | +```sh |
| 28 | +git status --short --branch |
| 29 | +git fetch origin --prune |
| 30 | +``` |
| 31 | + |
| 32 | +List open PRs with the fields that matter: |
| 33 | + |
| 34 | +```sh |
| 35 | +gh pr list --state open --limit 100 \ |
| 36 | + --json number,title,author,baseRefName,headRefName,isDraft,mergeable,mergeStateStatus,reviewDecision,autoMergeRequest,statusCheckRollup,url |
| 37 | +``` |
| 38 | + |
| 39 | +Classify PRs into four buckets: |
| 40 | + |
| 41 | +1. **Merge candidates**: non-draft, `MERGEABLE`, `CLEAN`, no failing checks, not `CHANGES_REQUESTED`. |
| 42 | +2. **Review candidates**: clean but unapproved; inspect before approving. |
| 43 | +3. **Stack repair**: wrong base, conflicted, stale parent, or child branch blocked by a parent merge. |
| 44 | +4. **Leave alone**: draft, changes requested, branch owned by someone else, or unclear intent. |
| 45 | + |
| 46 | +For a focused PR, inspect the exact relationship: |
| 47 | + |
| 48 | +```sh |
| 49 | +gh pr view <n> --json number,title,author,baseRefName,headRefName,headRefOid,mergeable,mergeStateStatus,reviewDecision,autoMergeRequest,statusCheckRollup,url |
| 50 | +``` |
| 51 | + |
| 52 | +## Merge Or Queue Safely |
| 53 | + |
| 54 | +Merge bottom-up. After each merge, re-read the next child because the base may have changed. |
| 55 | + |
| 56 | +For intermediate stack branches: |
| 57 | + |
| 58 | +```sh |
| 59 | +gh pr merge <n> --auto --squash --delete-branch=false |
| 60 | +``` |
| 61 | + |
| 62 | +For a main-branch PR with merge queue behavior: |
| 63 | + |
| 64 | +```sh |
| 65 | +gh pr merge <n> --auto --delete-branch=false |
| 66 | +``` |
| 67 | + |
| 68 | +Verify immediately: |
| 69 | + |
| 70 | +```sh |
| 71 | +gh pr view <n> --json number,state,mergedAt,mergedBy,mergeCommit,mergeStateStatus,autoMergeRequest,url,title |
| 72 | +``` |
| 73 | + |
| 74 | +If the command reports an error that implies a race, stale base, or already-merged PR, re-read the PR before retrying. GitHub may have completed the merge while the CLI reported a confusing GraphQL error. |
| 75 | + |
| 76 | +## Review Before Approval |
| 77 | + |
| 78 | +For a clean but unapproved PR: |
| 79 | + |
| 80 | +```sh |
| 81 | +gh pr diff <n> --name-only |
| 82 | +gh pr diff <n> --patch |
| 83 | +``` |
| 84 | + |
| 85 | +Run the smallest relevant check in a clean checkout or temporary worktree. Approve only after the diff and check match the intended change: |
| 86 | + |
| 87 | +```sh |
| 88 | +gh pr review <n> --approve --body "Reviewed diff and validated with <command>." |
| 89 | +``` |
| 90 | + |
| 91 | +Then merge or queue it using the rules above. |
| 92 | + |
| 93 | +## Repair A Conflicted Child |
| 94 | + |
| 95 | +Prefer stack tooling when available: |
| 96 | + |
| 97 | +```sh |
| 98 | +gh stack view |
| 99 | +gh stack sync |
| 100 | +gh stack submit |
| 101 | +``` |
| 102 | + |
| 103 | +If using plain Git, repair in a throwaway worktree: |
| 104 | + |
| 105 | +```sh |
| 106 | +pr=123 |
| 107 | +base_branch=parent-branch |
| 108 | +head_branch=child-branch |
| 109 | +git fetch origin "$base_branch" "$head_branch" |
| 110 | +tmp=$(mktemp -d "/tmp/gradient-pr${pr}.XXXXXX") |
| 111 | +git worktree add -B "fix-pr${pr}" "$tmp" "origin/$head_branch" |
| 112 | +cd "$tmp" |
| 113 | +git rebase "origin/$base_branch" |
| 114 | +``` |
| 115 | + |
| 116 | +Resolve conflicts, choosing the final intended content rather than mechanically taking either side: |
| 117 | + |
| 118 | +```sh |
| 119 | +git status --short |
| 120 | +git add <file> |
| 121 | +git rebase --continue |
| 122 | +``` |
| 123 | + |
| 124 | +Run the relevant check. Push back with a lease: |
| 125 | + |
| 126 | +```sh |
| 127 | +git push --force-with-lease origin "fix-pr${pr}:$head_branch" |
| 128 | +``` |
| 129 | + |
| 130 | +Re-read the PR. Merge only when it returns to `MERGEABLE` and `CLEAN`. |
| 131 | + |
| 132 | +Clean temporary worktrees after use: |
| 133 | + |
| 134 | +```sh |
| 135 | +git worktree remove --force "$tmp" |
| 136 | +``` |
| 137 | + |
| 138 | +## Retarget Wrong Bases |
| 139 | + |
| 140 | +If the commits are correct but the PR targets the wrong parent: |
| 141 | + |
| 142 | +```sh |
| 143 | +gh pr edit <n> --base <correct-base-branch> |
| 144 | +gh pr view <n> --json number,baseRefName,headRefName,mergeable,mergeStateStatus,url |
| 145 | +``` |
| 146 | + |
| 147 | +If retargeting introduces conflicts, repair the branch against the new base before merging. |
| 148 | + |
| 149 | +## Done Criteria |
| 150 | + |
| 151 | +- Every acted-on PR has fresh `gh pr view` evidence after the action. |
| 152 | +- Merged PRs show `state: MERGED`, `mergedAt`, and a merge commit. |
| 153 | +- Queued PRs show auto-merge or merge-queue state expected by the repository. |
| 154 | +- Rewritten branches were pushed with `--force-with-lease`. |
| 155 | +- Local temporary worktrees are removed. |
| 156 | +- Final report lists merged, queued, repaired, and intentionally skipped PRs separately. |
0 commit comments