Skip to content

SSC377QE - 30fps only, no cropping, majestic unstable #847

SSC377QE - 30fps only, no cropping, majestic unstable

SSC377QE - 30fps only, no cropping, majestic unstable #847

Workflow file for this run

# Merge gate for the Qodo code-review agent.
#
# Mirrors OpenIPC/devourer's qodo-gate.yml, which has already been through the
# failure modes documented below. Keep the two in sync when either is fixed.
#
# Why: branch protection's "require conversation resolution" only blocks
# unresolved threads that EXIST at merge time. Qodo posts its review a minute
# or two after the PR opens, so a "merge when CI is green" flow can race past
# it. This required check stays red until (a) Qodo has reviewed the PR at least
# once — the first, whole-diff pass is the valuable one — and (b) every review
# thread Qodo opened is resolved.
#
# Deliberately NOT pinned to the current head: requiring a review of every
# follow-up commit turns each review-response push into a fresh summon, and
# each re-review re-scans the diff and opens a new batch of ever-smaller
# findings — an unbounded fix/re-review treadmill. The first review catches
# the substance; thread resolution keeps each finding accountable (address it
# or dismiss it with rationale, in the thread, before resolving); follow-up
# commits are maintainer judgment, exactly as with a human reviewer who does
# not re-review every fixup. This repo also leaves handle_push_trigger off in
# .pr_agent.toml for the same reason — re-review on demand with /review.
#
# Event-driven: re-evaluates when the PR updates, when a review is submitted,
# and when someone replies in a review thread. GitHub's workflow parser
# rejects the documented `pull_request_review_thread` trigger ("Unexpected
# value" — verified empirically on devourer, zero-job "workflow file issue"
# run), so plain thread resolution does not auto-retrigger — after resolving
# the last thread, leave a reply (retriggers); the check reads the live
# resolution state each run. Escape hatch for a Qodo outage: the
# `skip-qodo-gate` label passes the check (label changes re-trigger it).
#
# A passing run also re-runs this workflow's earlier FAILED runs on the same
# head commit. Each trigger event creates its own workflow run, and branch
# protection's rollup counts every run of a required check on the commit — a
# fresh green run sits beside the stale red ones rather than superseding
# them, so the PR stays BLOCKED until each red run is re-run by hand. Only a
# passing run re-runs others and a re-run that passes finds nothing red left,
# so it converges; if threads are genuinely unresolved the re-runs go red
# again and the gate still holds.
name: qodo-gate
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review, labeled, unlabeled]
pull_request_review:
types: [submitted]
pull_request_review_comment:
types: [created, deleted]
# A clean PR's only proof of review is an ISSUE comment, and without this
# trigger nothing re-evaluates the gate when it arrives: the run fired at
# `opened` fails (Qodo has not answered yet), the comment lands a minute or
# two later, and the check stays red until an unrelated push happens to
# re-trigger it. That is precisely how #2269 went green — a follow-up commit,
# not the review — which hid the gap on the PR that introduced it.
issue_comment:
types: [created, edited]
permissions:
contents: read
pull-requests: read
actions: write
jobs:
qodo-gate:
runs-on: ubuntu-latest
# Narrow the issue_comment trigger to the single comment that can change
# this gate's verdict: Qodo's review. Without all three clauses the job is
# an API-heavy workflow holding `actions: write` that any commenter could
# run at will, on any PR, as often as they liked — CI noise, and needless
# rate-limit exposure on a REQUIRED check.
#
# issue.pull_request — issue_comment fires for plain issues too, and
# those have no PR to gate.
# comment author — only the bot's own comment is evidence of review.
# A human typing /review still works: Qodo answers,
# and that answer is what triggers this.
# comment body — the bot also posts "Qodo is busy working" and
# "PR Summary by Qodo", neither of which means the
# diff was reviewed. Matching the review header
# keeps this to one run per review.
#
# Every other trigger here is PR-only and needs no guard.
if: >-
github.event_name != 'issue_comment' ||
(github.event.issue.pull_request &&
startsWith(github.event.comment.user.login, 'qodo-free-for-open-source-projects') &&
contains(github.event.comment.body, 'Code Review by Qodo'))
steps:
- name: Require a Qodo review with all its threads resolved
env:
GH_TOKEN: ${{ github.token }}
# issue_comment carries `issue`, not `pull_request` — for a PR
# comment the issue number IS the PR number.
PR: ${{ github.event.pull_request.number || github.event.issue.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
run: |
set -euo pipefail
BOT='qodo-free-for-open-source-projects'
json=$(gh api graphql \
-F owner="$REPO_OWNER" -F name="$REPO_NAME" -F pr="$PR" \
-f query='
query($owner: String!, $name: String!, $pr: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $pr) {
labels(first: 100) { nodes { name } }
}
}
}')
if echo "$json" | jq -e --arg l skip-qodo-gate \
'.data.repository.pullRequest.labels.nodes[] | select(.name == $l)' \
>/dev/null; then
echo "PASS: skip-qodo-gate label set (Qodo outage escape hatch)"
exit 0
fi
# Qodo signals a finished review in one of two ways, and a clean PR
# only ever produces the second:
#
# 1. a review OBJECT — created when it has inline comments to hang
# on the diff. Its later in-place updates edit the same object,
# so one existing == the PR has been reviewed.
# 2. an issue COMMENT headed "Code Review by Qodo" — which is ALL
# a zero-finding PR gets. Verified on #2269: "Bugs (0), Rule
# violations (0), Requirement gaps (0)", and no review object
# at all.
#
# Counting only (1) therefore blocks every clean PR forever — the
# gate can never go green precisely when there is nothing to fix.
#
# Paginated: a busy PR accumulates well over 100 review objects
# (every inline reply wraps itself in one), and the bot's first
# review is the OLDEST — exactly what a last-100 window loses
# first. --paginate applies --jq per page, so emit ids and count
# lines. REST spells the bot login with a [bot] suffix, unlike
# GraphQL, so match on the prefix.
reviewed=$(gh api "repos/$REPO_OWNER/$REPO_NAME/pulls/$PR/reviews" \
--paginate --jq ".[]
| select(.user.login | startswith(\"$BOT\"))
| .id" | wc -l)
# Match the review header specifically, NOT merely "a comment by the
# bot". Qodo also posts "Qodo is busy working" (a placeholder before
# it has read anything), "PR Summary by Qodo" (/describe output, not
# a review), and "failed to apply 'local' repo settings" (an error).
# Treating any of those as a review would let the gate pass on a PR
# the agent never actually reviewed.
commented=$(gh api "repos/$REPO_OWNER/$REPO_NAME/issues/$PR/comments" \
--paginate --jq ".[]
| select(.user.login | startswith(\"$BOT\"))
| select(.body | test(\"Code Review by Qodo\"; \"i\"))
| .id" | wc -l)
# Every PR hits this branch once: this run fires at `opened`, before
# Qodo has answered. That is not an error state, and the wording
# must not read like one — when Qodo's review lands, its comment
# re-triggers this gate and the passing run re-runs this failed one,
# so the check turns green with no action from anyone (observed
# 2-10 minutes on #2273/#2274/#2275, every one via re-run attempt 2).
# Only a much longer red deserves a human's attention.
if [ "$reviewed" -eq 0 ] && [ "$commented" -eq 0 ]; then
echo "EXPECTED RED: Qodo has not posted its review yet."
echo ""
echo "This is the normal state right after a PR is opened or updated."
echo "No action is needed: when Qodo's review lands (usually within"
echo "2-10 minutes), this check re-evaluates and turns green by"
echo "itself. You do not need to re-run it."
echo ""
echo "Still red after ~20 minutes? Then intervene:"
echo " - comment '/review' on the PR to summon Qodo again"
echo " - if Qodo is down, a maintainer can apply the"
echo " 'skip-qodo-gate' label, which passes this check"
exit 1
fi
echo "review found: $reviewed review object(s), $commented review comment(s)"
# Unresolved Qodo threads, paginated (a long-lived PR can exceed one
# 100-thread page; a truncated read must never produce a false pass).
unresolved=0
cursor=""
while :; do
args=( -F owner="$REPO_OWNER" -F name="$REPO_NAME" -F pr="$PR" )
[ -n "$cursor" ] && args+=( -F cursor="$cursor" )
page=$(gh api graphql "${args[@]}" \
-f query='
query($owner: String!, $name: String!, $pr: Int!, $cursor: String) {
repository(owner: $owner, name: $name) {
pullRequest(number: $pr) {
reviewThreads(first: 100, after: $cursor) {
pageInfo { hasNextPage endCursor }
nodes {
isResolved
comments(first: 10) { nodes { author { login } } }
}
}
}
}
}')
# A thread is Qodo's if ANY of its first comments is by the bot —
# first-comment-only attribution loses the thread when the bot's
# opening comment is deleted while replies remain (and deletion
# re-triggers this check, so that would be a false pass).
n=$(echo "$page" | jq --arg b "$BOT" \
'[.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved | not)
| select([.comments.nodes[].author.login] | index($b))] | length')
unresolved=$((unresolved + n))
more=$(echo "$page" | jq -r \
'.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
[ "$more" = "true" ] || break
cursor=$(echo "$page" | jq -r \
'.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
done
if [ "$unresolved" -gt 0 ]; then
echo "FAIL: $unresolved unresolved Qodo review thread(s) — address"
echo "or explicitly dismiss each finding in its thread, then mark"
echo "it resolved. Plain resolution does not auto-retrigger this"
echo "check: leave a reply in a thread (retriggers) — the passing"
echo "run then sweeps this red run off the commit itself."
exit 1
fi
echo "PASS: Qodo review present, all its threads resolved"
# Sweep stale red runs of this gate off the head commit, so the pass
# above is the one the branch-protection rollup sees. Best-effort: a
# failed re-run request must not turn a PASS into a FAIL.
- name: Re-run this gate's earlier failed runs on this commit
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
# Empty on issue_comment, which carries no head sha — resolved from
# the PR below. This is the trigger that matters most for the sweep:
# it is the one that turns a clean PR's earlier red run green.
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR: ${{ github.event.pull_request.number || github.event.issue.number }}
THIS_RUN: ${{ github.run_id }}
run: |
set -u
if [ -z "$HEAD_SHA" ]; then
if ! HEAD_SHA=$(gh pr view "$PR" --repo "$REPO" --json headRefOid \
--jq .headRefOid); then
echo "sweep skipped: could not resolve head sha for PR $PR"
exit 0
fi
fi
# The listing is guarded too, not just the reruns: with an unguarded
# pipeline a transient list failure would be the step's exit code —
# exactly the PASS-into-FAIL this step promises not to produce.
if ! ids=$(gh run list --repo "$REPO" --workflow qodo-gate \
--commit "$HEAD_SHA" --json databaseId,conclusion \
--jq '.[] | select(.conclusion == "failure") | .databaseId'); then
echo "sweep skipped: could not list this workflow's runs"
exit 0
fi
for id in $ids; do
[ "$id" = "$THIS_RUN" ] && continue
echo "re-running failed qodo-gate run $id"
gh run rerun "$id" --repo "$REPO" --failed || true
done
exit 0