Auto-merge
Merge a PR that was just promoted from draft to ready-for-review,
only when the change is small, non-disruptive, and every required
check is green. This skill is the natural successor to mark-pr-ready.
When to load this skill
Load after the mark-pr-ready skill has run (or after a
pull_request.ready_for_review event). Do not load it for PRs
that were created as ready-for-review from the start — only for PRs
that transitioned from draft.
Preconditions (all must be true)
- The PR is open and marked ready for review (not draft).
- The PR targets the repo's default branch.
- All CI checks have completed and passed.
- The diff is small and non-disruptive (see size gate below).
- No reviewer has requested changes.
- No unresolved review threads.
- At least 10 minutes have passed since the PR was marked ready
for review, with no new reviewer comments or change requests
during that window.
If any precondition fails, stop — do not merge. For precondition 7,
if the quiet period hasn't elapsed yet, schedule a one-shot follow-up
for the remaining time (Flue Durable Object scheduleFollowUp /
platform schedule(), or a run_once timer when running in-container)
and stop. The follow-up will re-trigger this skill when the period is up.
Size gate
Classify the PR as "small and non-disruptive" only when all of
these hold:
- Total lines changed (additions + deletions) ≤ 150.
- No more than 5 files changed.
- No changes to CI/CD configuration (
.github/workflows/, Dockerfile,
docker-compose*, Makefile, Justfile, Terraform *.tf).
- No changes to dependency lockfiles (
bun.lock, package-lock.json,
yarn.lock, pnpm-lock.yaml, Cargo.lock, go.sum).
- No database migrations or schema changes.
- No changes to authentication, authorization, or secrets handling.
- No deletions of public API surface (exported functions, REST
endpoints, GraphQL types).
If the PR exceeds the size gate, stop. Post a comment noting the PR
needs human review and list which criteria it exceeded.
Workflow
Check quiet period. Verify the PR was marked ready at least
10 minutes ago with no reviewer activity since:
READY_AT=$(gh api "repos/<owner>/<repo>/issues/<N>/timeline" --paginate \
--jq '[.[] | select(.event=="ready_for_review")] | last | .created_at')
Calculate elapsed time. If less than 10 minutes have passed,
schedule a one-shot follow-up (Flue DO scheduleFollowUp / platform
schedule(), or in-container timer) for the remaining time with
entity_key set to the PR entity, and stop. The follow-up prompt
should instruct the agent to reload the auto-merge skill.
Also check for any reviewer comments or changes_requested
reviews that arrived after READY_AT:
gh api "repos/<owner>/<repo>/pulls/<N>/reviews" \
--jq '[.[] | select(.submitted_at > "'$READY_AT'" and .state != "APPROVED" and .state != "COMMENTED")]'
If any exist, stop — the PR needs human attention.
Verify PR state and target branch:
PR_JSON=$(gh pr view <N> --json state,isDraft,baseRefName)
STATE=$(echo "$PR_JSON" | jq -r '.state')
IS_DRAFT=$(echo "$PR_JSON" | jq -r '.isDraft')
BASE=$(echo "$PR_JSON" | jq -r '.baseRefName')
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
- Expect
STATE=OPEN, IS_DRAFT=false. If draft or closed, stop.
- Expect
BASE == DEFAULT_BRANCH. If the PR targets a release or
other protected branch, stop — those need human review.
Verify all checks pass:
CHECKS=$(gh pr view <N> --json statusCheckRollup \
--jq '.statusCheckRollup')
For each check, inspect status and conclusion:
- If any check has
status other than "COMPLETED" (e.g.
"QUEUED", "IN_PROGRESS", "PENDING"), stop — checks
haven't finished yet. Post a comment noting which checks are
still running.
- If any completed check has
conclusion other than "SUCCESS",
"SKIPPED", or "NEUTRAL", stop — checks are failing. Post a
comment listing the failing checks.
Quick jq filter for non-passing completed checks:
FAILING=$(echo "$CHECKS" | jq '[.[] | select(
.status == "COMPLETED" and
.conclusion != "SUCCESS" and
.conclusion != "SKIPPED" and
.conclusion != "NEUTRAL"
)]')
Quick jq filter for still-running checks:
PENDING=$(echo "$CHECKS" | jq '[.[] | select(.status != "COMPLETED")]')
Evaluate the size gate:
PR_DATA=$(gh pr view <N> --json additions,deletions,files)
ADDITIONS=$(echo "$PR_DATA" | jq '.additions')
DELETIONS=$(echo "$PR_DATA" | jq '.deletions')
TOTAL=$((ADDITIONS + DELETIONS))
FILES_CHANGED=$(echo "$PR_DATA" | jq '.files | length')
Check each criterion listed in the size gate section. Inspect the
file list for CI/CD, lockfile, migration, auth, or public API
changes:
echo "$PR_DATA" | jq -r '.files[].path'
Check for review objections and unresolved threads:
CHANGES_REQUESTED=$(gh pr view <N> --json reviews \
--jq '[.reviews[] | select(.state == "CHANGES_REQUESTED")] | length')
If CHANGES_REQUESTED > 0, stop — a reviewer has requested changes.
Check for unresolved review threads via the GraphQL API:
UNRESOLVED=$(gh api graphql -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes { isResolved }
}
}
}
}' -f owner=<OWNER> -f repo=<REPO> -F pr=<N> \
--jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length')
If UNRESOLVED > 0, stop — there are unresolved review threads.
Merge:
gh pr merge <N> --squash --auto --delete-branch
Use --squash to keep the main branch history clean.
Use --auto so GitHub waits for branch protection rules.
Use --delete-branch to clean up the feature branch.
Post a short comment confirming the merge was enabled. Mention
the total diff size and that all checks passed. Write it
naturally — vary the wording, don't use a canned phrase.
When NOT to merge
- The PR has "CHANGES_REQUESTED" reviews.
- The PR has unresolved review threads.
- The PR modifies security-sensitive code.
- The PR exceeds the size gate.
- Any required check is not green.
- Any check is still running (not yet completed).
- The PR targets a branch other than the repo's default branch.
In all these cases, leave a comment explaining why auto-merge was
skipped, and let a human decide.
Notes
- This skill should be loaded by the coordinator after
mark-pr-ready
completes, or in response to a pull_request.ready_for_review webhook.
- The
--auto flag on gh pr merge respects branch protection rules.
If the repo requires approvals, the merge will wait until those are
satisfied.
- Never force-merge or bypass branch protection.
1---2name: auto-merge3description: Auto-merge a PR after it is marked ready-for-review, if the change is small, non-disruptive, and all checks pass.4license: Apache-2.05---67# Auto-merge89Merge a PR that was just promoted from draft to ready-for-review,10**only** when the change is small, non-disruptive, and every required11check is green. This skill is the natural successor to `mark-pr-ready`.1213## When to load this skill1415Load after the `mark-pr-ready` skill has run (or after a16`pull_request.ready_for_review` event). Do **not** load it for PRs17that were created as ready-for-review from the start — only for PRs18that transitioned from draft.1920## Preconditions (all must be true)21221. The PR is open and marked ready for review (not draft).232. The PR targets the repo's default branch.243. All CI checks have completed and passed.254. The diff is small and non-disruptive (see size gate below).265. No reviewer has requested changes.276. No unresolved review threads.287. At least 10 minutes have passed since the PR was marked ready29 for review, with no new reviewer comments or change requests30 during that window.3132If any precondition fails, stop — do not merge. For precondition 7,33if the quiet period hasn't elapsed yet, schedule a one-shot follow-up34for the remaining time (Flue Durable Object `scheduleFollowUp` /35platform `schedule()`, or a `run_once` timer when running in-container)36and stop. The follow-up will re-trigger this skill when the period is up.3738## Size gate3940Classify the PR as "small and non-disruptive" only when **all** of41these hold:4243- Total lines changed (additions + deletions) ≤ 150.44- No more than 5 files changed.45- No changes to CI/CD configuration (`.github/workflows/`, `Dockerfile`,46 `docker-compose*`, `Makefile`, `Justfile`, Terraform `*.tf`).47- No changes to dependency lockfiles (`bun.lock`, `package-lock.json`,48 `yarn.lock`, `pnpm-lock.yaml`, `Cargo.lock`, `go.sum`).49- No database migrations or schema changes.50- No changes to authentication, authorization, or secrets handling.51- No deletions of public API surface (exported functions, REST52 endpoints, GraphQL types).5354If the PR exceeds the size gate, stop. Post a comment noting the PR55needs human review and list which criteria it exceeded.5657## Workflow58590. **Check quiet period**. Verify the PR was marked ready at least60 10 minutes ago with no reviewer activity since:61 ```sh62 READY_AT=$(gh api "repos/<owner>/<repo>/issues/<N>/timeline" --paginate \63 --jq '[.[] | select(.event=="ready_for_review")] | last | .created_at')64 ```65 Calculate elapsed time. If less than 10 minutes have passed,66 schedule a one-shot follow-up (Flue DO `scheduleFollowUp` / platform67 `schedule()`, or in-container timer) for the remaining time with68 `entity_key` set to the PR entity, and stop. The follow-up prompt69 should instruct the agent to reload the `auto-merge` skill.7071 Also check for any reviewer comments or `changes_requested`72 reviews that arrived after `READY_AT`:73 ```sh74 gh api "repos/<owner>/<repo>/pulls/<N>/reviews" \75 --jq '[.[] | select(.submitted_at > "'$READY_AT'" and .state != "APPROVED" and .state != "COMMENTED")]'76 ```77 If any exist, stop — the PR needs human attention.78791. **Verify PR state and target branch**:80 ```sh81 PR_JSON=$(gh pr view <N> --json state,isDraft,baseRefName)82 STATE=$(echo "$PR_JSON" | jq -r '.state')83 IS_DRAFT=$(echo "$PR_JSON" | jq -r '.isDraft')84 BASE=$(echo "$PR_JSON" | jq -r '.baseRefName')85 DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)86 ```87 - Expect `STATE=OPEN`, `IS_DRAFT=false`. If draft or closed, stop.88 - Expect `BASE == DEFAULT_BRANCH`. If the PR targets a release or89 other protected branch, stop — those need human review.90912. **Verify all checks pass**:92 ```sh93 CHECKS=$(gh pr view <N> --json statusCheckRollup \94 --jq '.statusCheckRollup')95 ```96 For each check, inspect `status` and `conclusion`:97 - If any check has `status` other than `"COMPLETED"` (e.g.98 `"QUEUED"`, `"IN_PROGRESS"`, `"PENDING"`), stop — checks99 haven't finished yet. Post a comment noting which checks are100 still running.101 - If any completed check has `conclusion` other than `"SUCCESS"`,102 `"SKIPPED"`, or `"NEUTRAL"`, stop — checks are failing. Post a103 comment listing the failing checks.104105 Quick jq filter for non-passing completed checks:106 ```sh107 FAILING=$(echo "$CHECKS" | jq '[.[] | select(108 .status == "COMPLETED" and109 .conclusion != "SUCCESS" and110 .conclusion != "SKIPPED" and111 .conclusion != "NEUTRAL"112 )]')113 ```114 Quick jq filter for still-running checks:115 ```sh116 PENDING=$(echo "$CHECKS" | jq '[.[] | select(.status != "COMPLETED")]')117 ```1181193. **Evaluate the size gate**:120 ```sh121 PR_DATA=$(gh pr view <N> --json additions,deletions,files)122 ADDITIONS=$(echo "$PR_DATA" | jq '.additions')123 DELETIONS=$(echo "$PR_DATA" | jq '.deletions')124 TOTAL=$((ADDITIONS + DELETIONS))125 FILES_CHANGED=$(echo "$PR_DATA" | jq '.files | length')126 ```127 Check each criterion listed in the size gate section. Inspect the128 file list for CI/CD, lockfile, migration, auth, or public API129 changes:130 ```sh131 echo "$PR_DATA" | jq -r '.files[].path'132 ```1331344. **Check for review objections and unresolved threads**:135 ```sh136 CHANGES_REQUESTED=$(gh pr view <N> --json reviews \137 --jq '[.reviews[] | select(.state == "CHANGES_REQUESTED")] | length')138 ```139 If `CHANGES_REQUESTED > 0`, stop — a reviewer has requested changes.140141 Check for unresolved review threads via the GraphQL API:142 ```sh143 UNRESOLVED=$(gh api graphql -f query='144 query($owner: String!, $repo: String!, $pr: Int!) {145 repository(owner: $owner, name: $repo) {146 pullRequest(number: $pr) {147 reviewThreads(first: 100) {148 nodes { isResolved }149 }150 }151 }152 }' -f owner=<OWNER> -f repo=<REPO> -F pr=<N> \153 --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length')154 ```155 If `UNRESOLVED > 0`, stop — there are unresolved review threads.1561575. **Merge**:158 ```sh159 gh pr merge <N> --squash --auto --delete-branch160 ```161 Use `--squash` to keep the main branch history clean.162 Use `--auto` so GitHub waits for branch protection rules.163 Use `--delete-branch` to clean up the feature branch.1641656. **Post a short comment** confirming the merge was enabled. Mention166 the total diff size and that all checks passed. Write it167 naturally — vary the wording, don't use a canned phrase.168169## When NOT to merge170171- The PR has "CHANGES_REQUESTED" reviews.172- The PR has unresolved review threads.173- The PR modifies security-sensitive code.174- The PR exceeds the size gate.175- Any required check is not green.176- Any check is still running (not yet completed).177- The PR targets a branch other than the repo's default branch.178179In all these cases, leave a comment explaining why auto-merge was180skipped, and let a human decide.181182## Notes183184- This skill should be loaded by the coordinator after `mark-pr-ready`185 completes, or in response to a `pull_request.ready_for_review` webhook.186- The `--auto` flag on `gh pr merge` respects branch protection rules.187 If the repo requires approvals, the merge will wait until those are188 satisfied.189- Never force-merge or bypass branch protection.