From 6dab0f29b52303a53eac6eb2971101f2b4282bd0 Mon Sep 17 00:00:00 2001 From: Ramona-2416 Date: Mon, 19 Oct 2026 23:32:26 -0400 Subject: [PATCH] ci(greptile): label a pull request two minutes after its last update On Oct 19 a greptile label added seven seconds after a pull request opened started no review, on both pull requests where that happened: anima-research/context-manager#268, labelled by this workflow, and anima-research/eidoverse-worlds#266, labelled by hand. On Oct 18, labels added 35 to 105 seconds after opening each started one, on seven pull requests. The cause is inferred from those cases. The workflow now reads each pull request's updatedAt, and a run labels a pull request only once two minutes have passed since its last update. One updated less than two minutes before a run is left for a later run, usually the next, and the run's log names it if it's past 400 lines or has no line counts. The wait counts from any update rather than only from the openings Greptile names (opened, reopened, ready for review), because a label Greptile drops stays on the pull request, so no later run adds it again, while waiting delays the label by a run each time the pull request is updated in the two minutes before one. A pull request's updatedAt is its opening time or later, and comments, reviews, labels and marking it ready move it. Reopening and pushing are expected to move it too, though that hasn't been checked. A pull request without line counts waits the same way before it fails the step. One whose updatedAt doesn't parse gets no wait, as before this change, and a warning names it if it's past 400 lines or has no line counts. CONTRIBUTING's Greptile bullet says a pull request updated in the two minutes before a run waits for a later one, and that a maintainer adds the label by hand a minute or more after a pull request is opened, reopened or marked ready. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/greptile-label.yml | 85 +++++++++++++++++++++------- CONTRIBUTING.md | 11 ++-- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/.github/workflows/greptile-label.yml b/.github/workflows/greptile-label.yml index 999aab0cb..9275a39cb 100644 --- a/.github/workflows/greptile-label.yml +++ b/.github/workflows/greptile-label.yml @@ -3,28 +3,48 @@ name: Greptile label # Greptile reviews only pull requests labelled `greptile` (.greptile/config.json). # Every 30 minutes this job adds the label to each open, non-draft pull # request with more than 400 additions plus deletions that carries neither -# `greptile` nor `no-greptile`, so a smaller one gets a Greptile review only -# if someone adds the label by hand. Greptile's reference lists a pull -# request being given a trigger label among the openings it reviews, and a -# label added after a pull request opened starts its review then: within -# about half an hour of a pull request opening, passing 400 lines or being -# marked ready, and later when GitHub's scheduler is busy. +# `greptile` nor `no-greptile` and hasn't been updated in the last two +# minutes, so a smaller one gets a Greptile review only if someone adds the +# label by hand. Greptile's reference lists a pull request being given a +# trigger label among the openings it reviews, so its review starts when this +# job's label lands: within about half an hour of a pull request opening, +# passing 400 lines or being marked ready, and later when GitHub's scheduler +# is busy. # -# Each run lists every open pull request, up to the list call's 500, so the -# next run catches up on one that was missed or delayed. A draft past the -# line is labelled once it's marked ready. The label is only ever added: a +# A run labels a pull request only once two minutes have passed since its +# last update, so one updated less than two minutes before a run waits for a +# later run, usually the next, about half an hour later. On Oct 19, a label +# added seven seconds after opening started no review on either of the two +# pull requests it happened to, while on Oct 18 labels added 35 to 105 seconds +# after opening started a review on each of seven. The two minutes are a +# margin over those cases, since the boundary between seven and 35 seconds +# hasn't been measured. The wait counts from any update, not only from the +# openings Greptile's reference names (opened, reopened, ready for review), +# because a label Greptile drops stays on the pull request, so no later run +# adds it again, while waiting delays the label by a run each time the pull +# request is updated in the two minutes before one. A pull request's update +# time is when it opened or later, and comments, reviews, labels and marking +# it ready move it. Reopening it and pushing to it are expected to move it as +# well, though that hasn't been checked. A pull request whose update time +# doesn't parse gets no wait, and a warning in the run's log names it if it's +# past 400 lines or has no line counts. +# +# Each run lists every open pull request, up to the list call's 500, so a +# later run catches up on one that was missed or delayed. A draft past the +# line is labelled after it's marked ready. The label is only ever added: a # pull request that later shrinks keeps it, and removing it from one past the -# line doesn't skip the review, since the next run adds it back. -# `no-greptile` is the way to skip a review. +# line doesn't skip the review, since a later run adds it back. `no-greptile` +# is the way to skip a review. # # A schedule rather than pull_request_target, which GitHub's default event # policy for public repositories blocks once it's enforced, from November 2, # 2026. A scheduled run uses this file as the default branch has it, reads -# pull request numbers, line counts, draft flags and labels through the API, -# and checks nothing out. GitHub disables a public repository's scheduled -# workflows after 60 days without activity (enable this one again from the -# Actions tab), and sends a scheduled workflow's notifications to whoever last -# changed its cron line. workflow_dispatch runs it on demand. +# pull request numbers, line counts, draft flags, labels and update times +# through the API, and checks nothing out. GitHub disables a public +# repository's scheduled workflows after 60 days without activity (enable this +# one again from the Actions tab), and sends a scheduled workflow's +# notifications to whoever last changed its cron line. workflow_dispatch runs +# it on demand. on: schedule: # Away from the start of the hour, which GitHub names as a time of high @@ -49,21 +69,36 @@ jobs: # names compare case-insensitively, as Greptile's own filters do. jq # reads a missing count as null, and null plus a number as that # number, so a candidate whose counts aren't both numbers is named and - # fails the step rather than passing unlabelled. A label that fails for - # one pull request doesn't stop the others. + # fails the step rather than passing unlabelled, once two minutes have + # passed since its last update (at once if that time doesn't parse). A + # label that fails for one pull request doesn't stop the others. run: | prs=$(gh pr list --repo "$REPO" --state open --limit 500 \ - --json number,additions,deletions,isDraft,labels) + --json number,additions,deletions,isDraft,labels,updatedAt) open=$(jq length <<< "$prs") echo "Open pull requests: $open." candidates=$(jq '[.[] | select(.isDraft | not) | select(any(.labels[]; (.name | ascii_downcase) == "greptile" or (.name | ascii_downcase) == "no-greptile") | not)]' <<< "$prs") - uncounted=$(jq -r '[.[] | select((.additions | type) != "number" or (.deletions | type) != "number") + # A pull request updated in the last two minutes waits for a later + # run, whether or not it has line counts yet. One whose updatedAt + # doesn't parse gets no wait, as before this rule, and the warning + # names it if it's past 400 lines or has no line counts. + now=$(date -u +%s) + untimed=$(jq -r '[.[] | select(((.updatedAt | fromdateiso8601?) // null) == null) + | select((.additions | type) != "number" or (.deletions | type) != "number" or .additions + .deletions > 400) + | "#\(.number)"] | join(" ")' <<< "$candidates") + settled=$(jq --argjson now "$now" '[.[] + | select(((.updatedAt | fromdateiso8601?) // 0) <= $now - 120)]' <<< "$candidates") + young=$(jq -r --argjson now "$now" '[.[] + | select(((.updatedAt | fromdateiso8601?) // 0) > $now - 120) + | select((.additions | type) != "number" or (.deletions | type) != "number" or .additions + .deletions > 400) | "#\(.number)"] | join(" ")' <<< "$candidates") + uncounted=$(jq -r '[.[] | select((.additions | type) != "number" or (.deletions | type) != "number") + | "#\(.number)"] | join(" ")' <<< "$settled") todo=$(jq -r '.[] | select((.additions | type) == "number" and (.deletions | type) == "number") | select(.additions + .deletions > 400) - | "\(.number) \(.additions + .deletions)"' <<< "$candidates") + | "\(.number) \(.additions + .deletions)"' <<< "$settled") labelled=0 failed="" while read -r number changed; do @@ -77,11 +112,17 @@ jobs: fi done <<< "$todo" echo "Labelled $labelled." + if [ -n "$young" ]; then + echo "Left for a later run, updated less than two minutes ago: $young." + fi + if [ -n "$untimed" ]; then + echo "::warning::GitHub gave no readable update time for these, so they got no two-minute wait: $untimed." + fi if [ -n "$uncounted" ]; then echo "::error::GitHub gave no line counts for these, so they weren't labelled: $uncounted. To have Greptile review one, add the greptile label to it by hand, or add no-greptile to skip its review; either ends this failure for it." fi if [ -n "$failed" ]; then - echo "::error::Labelling failed for these:$failed. GitHub's answer for each is above its 'Labelling #N failed' line, and every later run tries again. To have Greptile review one now, add the greptile label to it by hand, creating the label from the repository's Labels page if it isn't there, or add no-greptile to skip its review; either ends this failure for it." + echo "::error::Labelling failed for these:$failed. GitHub's answer for each is above its 'Labelling #N failed' line, and later runs try again. To have Greptile review one now, add the greptile label to it by hand, creating the label from the repository's Labels page if it isn't there, or add no-greptile to skip its review; either ends this failure for it." fi if [ -n "$uncounted$failed" ]; then exit 1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23b3dcb07..60640fb41 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,10 +66,13 @@ the house default; plain descriptive titles are accepted. additions plus deletions pass 400 lines, unless it carries `no-greptile`, and the review starts when the label lands: within about half an hour of the PR opening, passing 400 lines or being marked ready, and later when - GitHub's scheduler is busy. A smaller PR gets a Greptile review if a - maintainer adds the label. `no-greptile` skips the review. Removing - `greptile` from a PR past 400 lines doesn't skip it, since the next run - adds the label back. + GitHub's scheduler is busy. A PR updated in the two minutes before a run + waits for a later one. A smaller PR gets a Greptile review if a maintainer + adds the label a minute or more after it's opened, reopened or marked + ready. A label added seven seconds after opening has twice started no + review, and Greptile's reference counts reopening and marking ready as + openings too. `no-greptile` skips the review. Removing `greptile` from a PR + past 400 lines doesn't skip it, since a later run adds the label back. - Respond by pushing fix commits and replying per finding — "Addressed in ``" — rather than force-pushing a rewritten branch. A re-review then flips the verdict.