fix: automerge ignora o próprio status do job (deadlock de self-wait) e exige o contexto checks
Some checks failed
CI / checks (pull_request) Successful in 22s
CI / deploy (pull_request) Has been skipped
Agent PR Ready + auto-merge / ready-automerge (pull_request) Failing after 6m34s

This commit is contained in:
Francisco Solla 2026-08-17 21:26:09 -03:00
parent 83c5012e6e
commit ca75a43b54
2 changed files with 19 additions and 5 deletions

View file

@ -46,6 +46,10 @@ try {
console.log(`[forgejo-pr-automerge] PR #${prNumber} já Ready`)
}
const merged = await api.autoMerge(prNumber, {
// The workflow job that runs this script reports its own status on the
// PR head sha — ignoring it avoids the self-wait deadlock. The required
// `checks` context is enforced by waitForChecks.
ignoreContexts: ['ready-automerge'],
log: (line) => console.log(`[forgejo-pr-automerge] ${line}`),
})
console.log(

View file

@ -271,22 +271,32 @@ export const createApi = ({ base, token, repository, fetchImpl } = {}) => {
body: { ...payload, branch_name: branch },
}),
/** All required statuses on the PR head are success (or a failure exists). */
waitForChecks: async (number, { timeoutMs = 30 * 60 * 1000, pollMs = 15000, log } = {}) => {
/**
* All required statuses on the PR head are success (or a failure exists).
* `ignoreContexts` excludes a job's OWN status — a workflow that merges a
* PR sees itself as `pending` on the head sha and would deadlock waiting
* for itself. `requiredContext` (default `checks`) must be success.
*/
waitForChecks: async (
number,
{ timeoutMs = 30 * 60 * 1000, pollMs = 15000, log, ignoreContexts = [], requiredContext = 'checks' } = {},
) => {
const started = Date.now()
for (;;) {
const pr = await api.getPullRequest(number)
if (!pr || pr.state !== 'OPEN' || pr.merged) return pr
const statuses = await api.listCommitStatuses(pr.head.sha)
const failed = statuses.filter(
const relevant = statuses.filter((entry) => !ignoreContexts.some((c) => entry.context.includes(c)))
const failed = relevant.filter(
(entry) => entry.state === 'failure' || entry.state === 'error',
)
const pending = statuses.filter((entry) => entry.state === 'pending')
const pending = relevant.filter((entry) => entry.state === 'pending')
if (failed.length > 0) {
const names = failed.map((entry) => entry.context).join(', ')
throw new Error(`Checks falharam no PR #${number}: ${names}`)
}
if (statuses.length > 0 && pending.length === 0) return pr
const required = relevant.find((entry) => entry.context === requiredContext)
if (required && required.state === 'success' && pending.length === 0) return pr
if (Date.now() - started > timeoutMs) {
throw new Error(`Timeout esperando checks do PR #${number}.`)
}