diff --git a/plugins/tend-ci-runner/skills/nightly/SKILL.md b/plugins/tend-ci-runner/skills/nightly/SKILL.md index 0913a3a9..f4143679 100644 --- a/plugins/tend-ci-runner/skills/nightly/SKILL.md +++ b/plugins/tend-ci-runner/skills/nightly/SKILL.md @@ -76,15 +76,39 @@ a one-line reason) plus a `_Last refreshed: _` footer. Updates: Find conflicted PRs from this bot and from upstream dependency bots: +Don't filter `gh pr list --json mergeable` on `== "CONFLICTING"`. `mergeable` is computed lazily: the first query after `main` moves returns `UNKNOWN` and only *enqueues* the computation, so a cold read reports a conflicted PR as clean. There is no blocking read — [the REST docs](https://docs.github.com/en/rest/pulls/pulls#get-a-pull-request) prescribe resubmitting the request until the value settles. Test-merge locally instead: `git merge-tree` answers the same question synchronously, in-process, with no retry loop. + ```bash BOT_LOGIN=$(gh api user --jq '.login') +git fetch --quiet origin main for author in "$BOT_LOGIN" app/dependabot app/renovate; do - gh pr list --author "$author" --json number,title,mergeable,headRefName,author \ - --jq '.[] | select(.mergeable == "CONFLICTING")' + out="/tmp/prs-${author//\//-}.json" # `app/dependabot` has a slash; strip it + # `--limit 100` is load-bearing: `gh pr list` defaults to 30 and truncates + # silently, so PR 31 onward would never be test-merged. + gh pr list --author "$author" --limit 100 --json number,title,headRefName,author > "$out" + # A failed query leaves `$out` empty, which reads as "no conflicts" — the + # same silent-miss shape. `[]` (no open PRs) is 3 bytes, a failure is 0. + [ -s "$out" ] || { echo "query for $author never landed — conflicts unverified"; continue; } + # One fetch for every head, forced because bot branches get force-pushed. + # `refs/pull/N/head` also covers PRs opened from a fork. + mapfile -t refs < <(jq -r '.[].number | "refs/pull/\(.)/head:refs/tend/pr/\(.)"' "$out") + [ "${#refs[@]}" -eq 0 ] || git fetch --quiet --force origin "${refs[@]}" + jq -r '.[] | "\(.number)\t\(.title)"' "$out" | while IFS=$'\t' read -r n title; do + # Clean merge: exit 0. Conflict: non-zero with the tree OID and conflicted + # paths on stdout. Anything else (ref never fetched, bad revision): non-zero + # with stdout empty — so test `$tree`, not the exit status alone. + if tree=$(git merge-tree --write-tree origin/main "refs/tend/pr/$n" 2>/dev/null); then + continue + elif [ -n "$tree" ]; then + echo "CONFLICTING: $author #$n $title" + else + echo "merge test never ran, conflicts unverified: $author #$n $title" + fi + done done ``` -Skip the rest of this step if none of the queries return anything. +Skip the rest of this step only when every query landed and nothing printed. A PR whose merge test never ran is not a clean one — investigate it and report it as unverified rather than counting it clean. ### Upstream dependency bots: trigger the bot's own rebase