Skip to content

Instantly share code, notes, and snippets.

@Drowze
Last active January 31, 2026 04:33
Show Gist options
  • Select an option

  • Save Drowze/068e0d896026410a05e72cf3583c6fcb to your computer and use it in GitHub Desktop.

Select an option

Save Drowze/068e0d896026410a05e72cf3583c6fcb to your computer and use it in GitHub Desktop.
Automatically approve & merge pull requests based on a GitHub search (which can be across repositories).
# Example:
# oh_auto_merge 'archived:False org:MyOrg is:pr state:open author:app/dependabot'
function oh_auto_merge --argument-names search_query
set -l graphql_query 'query SearchPullRequests($search_query: String!) {
viewer {
login
}
search(
query: $search_query
type: ISSUE
first: 50
) {
nodes {
... on PullRequest {
number
title
url
mergeable
baseRefName
reviews(states: APPROVED, first: 100) {
totalCount
nodes {
author {
login
}
}
}
headRepository {
branchProtectionRules(first: 10) {
nodes {
pattern
requiredApprovingReviewCount
}
}
}
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
contexts(last: 100) {
nodes {
... on CheckRun {
name
conclusion
}
... on StatusContext {
context
state
}
}
}
}
}
}
}
}
}
}
}'
# Helper function for colored logging
function __oh_log --argument-names color --argument-names message
set_color $color ; echo $message ; set_color normal
end
set -l response "$(gh api graphql -f "query=$graphql_query" -f "search_query=$search_query")"
set -l gh_viewer "$(echo "$response" | jq -r '.data.viewer.login')"
for pr in (echo "$response" | jq -c '.data.search.nodes[]')
__oh_log cyan "$(echo "$pr" | jq -r '"PR: \(.title) (#\(.number)) (\(.url))"')"
# Check mergeable status, retrying if UNKNOWN (since it is computed by a background job on demand)
set -l mergeable_status (echo "$pr" | jq -r '.mergeable')
if test "$mergeable_status" = "UNKNOWN"
set -l url (echo "$pr" | jq -r '.url')
for i in (seq 1 5)
__oh_log normal "├── waiting for mergeable status: attempt $i / 5"
sleep 2
set mergeable_status (gh pr view "$url" --json mergeable -q '.mergeable')
if test "$mergeable_status" != "UNKNOWN"
break
end
end
end
# Skip if the PR has merge conflicts
if test "$mergeable_status" = "CONFLICTING"
__oh_log yellow "└── skipping (merge conflicts)"
continue
end
# Skip if the PR has failing or pending statuses/checks
if echo "$pr" | jq -e '.commits.nodes[0].commit.statusCheckRollup.contexts.nodes | any(.conclusion == "FAILURE" or .conclusion == "PENDING")' > /dev/null
__oh_log yellow "└── skipping (checks not passed)"
continue
end
# Approve the PR if not already approved
set -l current_approvals "$(echo "$pr" | jq -r '.reviews.totalCount')"
if echo "$pr" | jq -e --arg gh_viewer "$gh_viewer" '.reviews.nodes | any(.author.login == $gh_viewer)' > /dev/null
__oh_log normal "├── skipping approval (already approved)"
else
__oh_log normal "├── approving ..."
echo gh pr review --approve (echo "$pr" | jq -r '.url')
set current_approvals (math "$current_approvals" + 1)
end
# Skip if not enough approvals (based on branch protection rules)
set -l required_approvals (echo "$pr" | jq -r '.baseRefName as $base_branch | .headRepository.branchProtectionRules.nodes[] | select(.pattern == $base_branch).requiredApprovingReviewCount')
if test -n "$required_approvals" && test "$current_approvals" -lt "$required_approvals"
__oh_log yellow "└── skipping merge (not enough approvals - $current_approvals/$required_approvals)"
continue
end
__oh_log green "└── merging ..."
echo gh pr merge --squash (echo "$pr" | jq -r '.url')
sleep 0.5 # to avoid rate limiting
end
# Cleanup helper function from global scope
functions -e __oh_log
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment