Merge pull request #96 from codex-bot-andresmgsl/build/95-labels-blind-sweep-warning

fix: surface wholly blind label sweeps
This commit is contained in:
Daniel Marin 2026-07-24 00:43:17 +01:00 committed by GitHub
commit 5af1538463
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 79 additions and 6 deletions

View file

@ -13,6 +13,8 @@ on:
types: [opened, reopened, ready_for_review, converted_to_draft, synchronize, labeled, unlabeled]
permissions:
contents: read
checks: read # mergeability/check-rollup read for PR state
statuses: read # commit-status rollup read for PR state
issues: write
pull-requests: write
jobs:

View file

@ -6,6 +6,7 @@ so entries say what changed, cite the issue, and stop.
## Unreleased
- `labels-reconcile` — grant callers private-repo check reads and warn when an entire PR sweep is blind (#95).
- `labels-reconcile` — the bootstrap now retires the six GitHub defaults `LABELS.md` publishes as deleted, tolerating both an already-absent label and a refused delete (#93).
- `issueflow-reconcile` — a triage-authored issue arrival stands down with exit 0 instead of killing the run before the sweep (#91).
- FLEET.md — the assignee's `attention` wake: one role-independent trigger ahead of every per-role list, one acked session per demand; a spec on paper until `duty.sh` polls it (#86).

View file

@ -60,6 +60,12 @@ run() { # every mutation goes through here — DRY_RUN=1 logs instead of doing
if [ -n "${DRY_RUN:-}" ]; then log "DRY_RUN: $*"; else "$@"; fi
}
blind_sweep_warning() { # $1 = unreadable PRs, $2 = all open PRs
if [ "$2" -gt 0 ] && [ "$1" -eq "$2" ]; then
echo "::warning::labels: every open PR was unreadable; grant checks: read and statuses: read in the caller (private repos do not imply them)"
fi
}
load_config() { # $1 = consumer labels.conf; panel is mandatory, scopes optional
local conf="$1" line panel_seen=false
[ -f "$conf" ] || {
@ -592,9 +598,13 @@ main() {
REPO_LABELS="$(gh label list -R "$REPO" --limit 200 --json name --jq '.[].name' 2>/dev/null || echo "")"
[ -z "$REPO_LABELS" ] && log "WARNING: could not read the label set — applying labels unfiltered"
local n
for n in $(gh pr list -R "$REPO" --state open --limit 100 --json number --jq '.[].number'); do
(
local n output status total=0 unreadable=0
while IFS= read -r n; do
[ -n "$n" ] || continue
total=$((total + 1))
status=0
output="$(
(
PR_JSON="$(gh api "repos/$REPO/pulls/$n")"
DRAFT="$(jq -r '.draft' <<<"$PR_JSON")"
AUTHOR="$(jq -r '.user.login' <<<"$PR_JSON")"
@ -623,8 +633,16 @@ main() {
exit 0
fi
reconcile_pr "$n"
) || log "#$n: reconcile failed — continuing with the remaining PRs"
done
) 2>&1
)" || status=$?
[ -n "$output" ] && printf '%s\n' "$output"
if grep -qxF "labels: #$n: could not read mergeability/checks — left alone this pass" <<<"$output"; then
unreadable=$((unreadable + 1))
elif [ "$status" -ne 0 ]; then
log "#$n: reconcile failed — continuing with the remaining PRs"
fi
done < <(gh pr list -R "$REPO" --state open --limit 100 --json number --jq '.[].number')
blind_sweep_warning "$unreadable" "$total"
log "reconciled."
}

View file

@ -256,6 +256,8 @@ on:
types: [opened, labeled, unlabeled, assigned, unassigned, closed]
permissions:
contents: read
checks: read # mergeability/check-rollup read for PR state
statuses: read # commit-status rollup read for PR state
issues: write
pull-requests: write
jobs:
@ -263,6 +265,11 @@ jobs:
uses: heavy-duty/ceremony/.github/workflows/labels.yml@<pinned-tag>
```
Naming any permission sets every unnamed permission to `none`. Public
repositories allow check data to be read regardless, but a private consumer
needs both explicit reads above; without them the failure appears as an empty
`state:*` axis on the board rather than a red workflow run.
The `issues:` trigger is **unreleased** and is not in `0.1.0`. A consumer
pinned to `0.1.0` omits it. Add it only when bumping every ceremony reference
to the first tag carrying ceremony#32; never mix refs to adopt it early.

View file

@ -38,6 +38,18 @@ rev() { # $1=login $2=state $3=commit $4=body $5=submitted_at → one review obj
reviews() { jq -s '.' <<<"$*"; } # collect review objects into an array
# -- a sweep-wide read failure is visible without changing any PR ------------
warning="$(blind_sweep_warning 3 3)"
expect "a wholly blind sweep warns" \
"::warning::labels: every open PR was unreadable; grant checks: read and statuses: read in the caller (private repos do not imply them)" \
"$warning"
expect "the blind warning names checks: read" named \
"$(grep -qF "checks: read" <<<"$warning" && echo named || echo missing)"
expect "the blind warning names statuses: read" named \
"$(grep -qF "statuses: read" <<<"$warning" && echo named || echo missing)"
expect "a partially blind sweep does not warn" "" "$(blind_sweep_warning 1 3)"
expect "a sweep with no open PRs does not warn" "" "$(blind_sweep_warning 0 0)"
# -- drafts are building, whoever is requested --------------------------------
DRAFT=true HEAD_SHA=head1 REQUESTED="" REVIEWS_JSON='[]'
expect "draft PR is building" state:building "$(decide_state)"
@ -592,6 +604,40 @@ expect "exactly one nudge across both sweeps" \
expect "no label edit across both sweeps names the ruling flag" \
no "$(grep -q 'needs-ruling' "$RTMP/edits" 2>/dev/null && echo yes || echo no)"
# -- the sweep wiring observes the existing per-PR skip without writing -------
blind_main_probe() {
(
GITHUB_EVENT_NAME=schedule
REPO=owner/repo
LABELS_CONF=.github/labels.conf
gh() {
if [ "$1" = label ] && [ "$2" = list ]; then
printf 'state:building\nstate:addressing\n'
elif [ "$1" = pr ] && [ "$2" = list ]; then
printf '101\n102\n'
elif [ "$1" = pr ] && [ "$2" = view ]; then
printf '{}\n'
elif [ "$1" = api ] && [[ "$*" = *"/reviews"* ]]; then
return 0
elif [ "$1" = api ]; then
jq -n --arg n "${*: -1}" \
'{draft:false,user:{login:"author"},head:{sha:"head"},labels:[],requested_reviewers:[],created_at:"2026-07-23T00:00:00Z"}'
elif [ "$1" = issue ] && [ "$2" = edit ]; then
printf 'MUTATION: %s\n' "$*"
fi
}
main
)
}
blind_main="$(blind_main_probe)"
expect "a wholly blind main sweep emits one actionable annotation" 1 \
"$(grep -c '^::warning::.*checks: read.*statuses: read' <<<"$blind_main")"
expect "a wholly blind main sweep leaves every PR untouched" no \
"$(grep -q '^MUTATION:' <<<"$blind_main" && echo yes || echo no)"
expect "the existing per-PR skip still runs for every blind PR" 2 \
"$(grep -c 'could not read mergeability/checks — left alone this pass' <<<"$blind_main")"
# ---------------------------------------------------------------------------
# bootstrap_labels retires the GitHub defaults (#93). LABELS.md published
# them as deleted at bootstrap; nothing deleted them — incubator's first
@ -754,6 +800,5 @@ for ev in schedule pull_request_target; do
expect "...and deletes nothing" \
no "$(grep -q '^delete ' "$EXEC/record" && echo yes || echo no)"
done
printf 'labels-reconcile tests: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]