| name | unblock-prs |
| description | Clear what is blocking the open pull requests that already have auto-merge enabled — rebase the one that is out-of-date with the base branch, watch its checks, fix what fails — one PR at a time, and never merge anything. Use when asked to unblock or look after the open PRs, rebase branches behind main, watch CI, or fix a failing check on a PR. |
Unblock open pull requests
Clear the obstacles in front of the pull requests that are already queued to
merge, so GitHub can merge them itself.
Scope. Only pull requests with auto-merge already enabled
(autoMergeRequest is not null). That flag is the author's statement that the
PR should land as soon as it is able to; everything else is still someone's work
in progress and is none of this skill's business.
Two jobs, and no third. Rebase a branch that is out-of-date with main, and
fix a failing check. Never merge. No gh pr merge, no --auto, no enabling
auto-merge on a PR that lacks it, no review approvals. When a PR is green and
still unmerged, report why — do not finish it by hand.
One at a time. Merging any PR moves main, which puts every other open
branch back to BEHIND. A batch rebase therefore runs a full CI matrix per
branch and throws all but the first away. Never rebase a second PR while one is
still in flight.
Invoking this skill is the explicit instruction CLAUDE.md asks for before
pushing: for one PR at a time, you may push to that PR's branch, force-pushing
with --force-with-lease. Nothing on main, and no merges.
The loop
Survey → pick one → rebase → watch → fix → it merges itself → survey again.
Stop and report instead of continuing when a failure needs a decision the user
has to make.
1. Survey
gh pr list --state open --json number,title,headRefName,baseRefName,isDraft,author,mergeStateStatus,autoMergeRequest \
--jq '[.[] | select(.autoMergeRequest != null)]'
That filter is the scope rule, and it does most of the exclusion by itself:
changeset-release/main — the version PR that changesets/action opens and
force-pushes on every push to main — carries no auto-merge, and a draft PR
cannot have one. Still drop any PR whose baseRefName is not main.
chore/pnpm-update does have auto-merge, enabled by the bot, so it is in scope —
but pnpm-update.yml force-pushes that branch daily. If it moves under you, do
not fight it: re-survey and take its new state.
mergeStateStatus says what is blocking each one:
BEHIND — exactly the "out-of-date with the base branch" banner. This is the
one the loop rebases.
BLOCKED — up to date, but a required check is failing or still pending, or a
review is missing. Go to step 3 or 4; do not rebase it, that only restarts the
same matrix.
DIRTY — the rebase will conflict. Step 2b.
UNKNOWN — GitHub has not finished computing the merge state. Query again a
few seconds later; do not read it as up to date.
CLEAN — nothing is blocking it and auto-merge is on, so it is already
merging. Leave it alone.
2. Pick exactly one, and rebase it
Take whichever PR the user named; otherwise the one most likely to go green
unattended — small, already reviewed, oldest first among equals. Everything else
waits, untouched. A PR sitting at BEHIND costs nothing.
2a. The normal case
gh pr update-branch --rebase <number>
This rebases server-side. It does not touch the working tree, needs no checkout,
and fails cleanly instead of leaving a half-finished rebase behind. It does not
merge anything, and it leaves auto-merge armed. Confirm the head commit actually
moved and that checks are queued on the new SHA:
gh pr view <number> --json headRefOid,mergeStateStatus,autoMergeRequest
gh pr checks <number>
If the local clone has that branch checked out, it is now stale. Only resync it
when git status --porcelain is empty — never discard uncommitted work:
git fetch origin <branch> && git reset --hard "origin/<branch>"
2b. When it conflicts
gh pr update-branch fails, or mergeStateStatus was DIRTY. Rebase locally,
from a clean working tree:
git fetch origin main
git switch <branch>
git rebase origin/main
git push --force-with-lease="<branch>:<sha-before-the-rebase>" origin <branch>
Name the expected SHA in --force-with-lease. A bare --force-with-lease
compares against the remote-tracking ref, which is only as fresh as the last
fetch. Resolve conflicts by keeping the intent of both sides; if the resolution
is not obvious, stop and ask rather than guessing. git rebase --abort puts
everything back.
pnpm-lock.yaml is the usual conflict on chore/pnpm-update. Do not hand-edit
it: take one side, then run pnpm install --no-frozen-lockfile and commit what
it writes.
Note that a force-push does not disarm auto-merge; if autoMergeRequest did come
back null, say so and stop rather than re-enabling it.
3. Watch the checks
Every workflow here triggers on push, so the rebase started a fresh matrix.
Watch it in the background:
gh pr checks <number> --watch --fail-fast --interval 30
--required narrows the output to the checks that gate the merge;
github/rulesets/main.json is that list, and it is shorter than what the PR
page shows. type-check (ws:test:browser), type-check (ws:test:cov) and
Validate PR title all run but are not required, so a red one there does not
hold the merge — fix it anyway, but do not report the PR as blocked on it.
When the required checks all pass, GitHub squashes and merges the PR on its own
(allow_auto_merge, squash_merge_commit_title: PR_TITLE) — that is the
intended ending, and nothing here should race it.
Do not start the next PR while this one is being watched.
4. Fix a failing check
Find the failing job and read only its failing step:
gh pr checks <number> --json name,bucket,link --jq '.[] | select(.bucket=="fail")'
gh run view --job <job-id> --log-failed
The check name is the command. style-check (X) and type-check (X) both
run pnpm run X at the repository root, so type-check (ws:lint:fix)
reproduces as pnpm run ws:lint:fix. Three checks do not follow that rule:
| Check | What to run |
|---|
test-node-versions (<ver>) | pnpm run ws:build then pnpm run ws:test on that Node |
backup-repository-settings | pnpm run repo-settings:backup, then look for a dirty tree |
Validate PR title | The PR title is not Conventional Commits — gh pr edit --title |
A squash merge takes the PR title as the commit title
(squash_merge_commit_title: PR_TITLE), so the fix for that last one is the
title itself, not the branch.
Three things about reproducing the rest:
type-check (*) runs pnpm run ws:build first in CI. Do the same locally
or they fail for the wrong reason. style-check (*) does not build, so
ws:doc and ws:check:ext run against whatever dist/ you already have.
type-check (ws:test:browser) needs Playwright — pnpm exec playwright install chromium — and CI retries it once before calling it failed, so a
single red run there may just be flake. Re-read the log before changing code.
- Every
style-check and type-check job ends with assert-repo-is-clean.
So fmt:full, ws:doc, codemod:full and ws:lint:fix fail by changing
files, and the fix is to run the command locally and commit what it wrote. A
green run of the command with a dirty tree afterwards is still a failure.
Reproduce locally before pushing — a speculative fix costs another full matrix,
which is the cost this whole loop exists to avoid. Fix the cause: CLAUDE.md
rules hold, so no file-level eslint-disable, no loosening eslint.config.mts,
no as any. Then pnpm run fmt,
commit with a Conventional Commits message, push to the branch, and go back to
watching. Auto-merge survives the push, so a green result merges the PR without
another command. Do not re-rebase for a fix unless main has moved.
type-check, lint-and-build-style and test-node-versions jobs all gate on
check-should-run-type-checks, which diffs against origin/main. A job that
reports green having skipped its steps is expected on a docs-only branch, not a
problem to chase.
5. It merged — then go round again
Confirm with gh pr view <number> --json state,mergedAt. The remote branch is
deleted automatically (delete_branch_on_merge); locally, git fetch --prune,
and if that branch was checked out, switch back to main and pull.
If every required check is green and the PR is still open, something outside this
skill's two jobs is holding it — a missing review (the ruleset requires one
approval and a Copilot review), a required check that never reported, auto-merge
disabled behind your back. Report that and stop. Do not merge it.
Once it does merge, main has moved and every remaining PR reads BEHIND again.
That is expected, not a regression. Return to step 1 and pick the next single PR.
6. Report
One line per PR, in the order handled: number, what was done (rebased and merged
by GitHub / rebased and waiting / fix pushed / left alone and why), and where its
checks stand. Name any PR left failing and what the failure is. Do not report a
run as green while checks are still pending, and say plainly which PRs were never
reached and which were out of scope for lacking auto-merge.