| type | Skill |
| name | Auto Merge |
| category | core |
| description | Automatically merge open PRs that have passing CI, no blocking reviews, and no conflicts |
| var | |
| tags | ["dev","meta"] |
${var} — Repo (owner/repo) to target. If empty, uses every repo in memory/watched-repos.md.
Env: AUTO_MERGE_DRY_RUN=1 logs intent without merging. MAX_AUTO_MERGE=N caps merges per run (default 3).
Merge open PRs that are fully green and pass an explicit safety policy. The policy exists because this skill runs autonomously with write access — a bug in the gate is a bug that ships to main.
Read memory/MEMORY.md and memory/watched-repos.md for repos to target.
Read the last 2 days of memory/logs/ to avoid re-logging PRs already merged.
Safety policy
A PR merges only when every one of the following holds:
- Author allowlist:
author.login is one of dependabot[bot], renovate[bot], github-actions[bot], OR appears under a ## Trusted Authors section in memory/watched-repos.md. No allowlist → only the three bot logins are eligible.
- Size cap:
additions + deletions ≤ 500. Override by applying the label auto-merge-large on the PR.
- Base branch:
baseRefName is main or master. Refuse any other target.
- Not a fork:
isCrossRepository == false (fork CI can be tampered with).
- Not draft:
isDraft == false.
- Not already queued:
autoMergeRequest == null (avoid fighting GitHub's native auto-merge if a human enabled it).
- No opt-out label: none of {
do-not-merge, wip, hold, needs-review, blocked} present.
- Mergeable state:
mergeStateStatus == "CLEAN" (this is stricter than mergeable == "MERGEABLE" — CLEAN additionally requires branch-protection gates to be satisfied).
- Reviews:
reviewDecision != "CHANGES_REQUESTED".
- Checks: every entry in
statusCheckRollup has conclusion in {SUCCESS, NEUTRAL, SKIPPED}. Any FAILURE, TIMED_OUT, CANCELLED, PENDING, or null conclusion disqualifies the PR.
- Retry cap: this PR has been attempted fewer than 3 times. A PR that has hit
MERGE_FAIL three times across runs is paused — repeated failure on a CLEAN-looking PR usually means something subtle (a required check that didn't surface, branch-protection drift, token scope drift). Surface it and stop looping.
Steps
-
Bootstrap state — per-PR retry counter lives in memory/topics/auto-merge-state.json:
mkdir -p memory/topics
[ -f memory/topics/auto-merge-state.json ] || echo '{"prs":{},"last_run":null}' > memory/topics/auto-merge-state.json
Schema:
{
"last_run": "2026-05-23T08:00:00Z",
"prs": {
"owner/repo#123": {
"first_seen": "2026-05-21T10:00:00Z",
"last_attempt": "2026-05-23T08:00:00Z",
"attempts": 2,
"last_outcome": "merge_failed",
"last_error": "Pull Request is in unstable state"
}
}
}
PR keys are <owner>/<repo>#<number> so state survives multi-repo runs. Cap to 50 most-recent entries (LRU by last_attempt). Validate with jq empty after write; restore from .bak on failure.
-
List open PRs for each watched repo with the full field set:
gh pr list -R owner/repo --state open --json number,title,author,isDraft,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup,autoMergeRequest,isCrossRepository,labels,additions,deletions,baseRefName
Network note
gh authenticates via the workflow's GITHUB_TOKEN — no curl needed. If gh pr merge fails with Resource not accessible by integration, the workflow token lacks merge permission on that repo; log once and notify at most once per 7 days (check memory/logs/ for prior notification) to avoid alert spam.
Constraints
- Never merge a PR whose author is not allowlisted, even if every other gate is green.
- Never bypass the size cap without the explicit
auto-merge-large label (set by a human, not a bot).
- Never auto-retry a
MERGE_FAIL within the same run — if the first merge attempt fails, log and move on.
- After 3 failed attempts across runs, stop retrying that PR. Surface it once via the retry-cap notification and let the operator investigate.
- Do not modify PR state other than merging (no comments, no label edits, no branch updates).
Running this as an agent-shipping loop
To close the loop on PRs the agent itself opens (from feature, external-feature, self-improve, etc.), add the agent's GitHub identity under a ## Trusted Authors section in memory/watched-repos.md:
## Trusted Authors
- aeon-bot
- claude-code[bot]
Once allowlisted, agent PRs flow through the same safety policy as bot PRs and get auto-merged on green CI. The retry cap protects against runaway behavior on a stuck PR.