This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new 1445c52 fix(pr-management-triage): post-filter on conclusion to find
action_required runs (#112)
1445c52 is described below
commit 1445c52bd2b0c39248b3e049df99f29714940a6f
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon May 11 04:00:46 2026 +0200
fix(pr-management-triage): post-filter on conclusion to find
action_required runs (#112)
The `?status=action_required` query parameter on the Actions runs
REST endpoint does not match runs awaiting maintainer approval.
GitHub returns those runs as `status: "completed"` with
`conclusion: "action_required"`, so `?status=action_required`
silently returns an empty result.
Replace the broken filter in all four places it was used
(`fetch-and-batch.md` mandatory index + three recipes in
`actions.md`) with `?per_page=N` and a jq post-filter on
`conclusion`. Add explanatory text in fetch-and-batch.md and
inline comments in the recipes so a future reader does not
reintroduce the broken filter.
The `gh api -X POST .../approve` mutation itself works fine
against runs in this state — only the discovery queries were
broken.
---
.claude/skills/pr-management-triage/actions.md | 29 ++++++++++++++--------
.../skills/pr-management-triage/fetch-and-batch.md | 13 +++++++++-
2 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/.claude/skills/pr-management-triage/actions.md
b/.claude/skills/pr-management-triage/actions.md
index 17cb3c7..fa94b23 100644
--- a/.claude/skills/pr-management-triage/actions.md
+++ b/.claude/skills/pr-management-triage/actions.md
@@ -189,10 +189,14 @@ the maintainer queue fills with PRs whose CI has not
actually
executed.
```bash
-# Pre-check: index action_required runs repo-wide, then look up head SHA
+# Pre-check: index action_required runs at the head SHA.
+# Note: runs awaiting approval are returned as `status: "completed"`
+# with `conclusion: "action_required"`. The query parameter
+# `?status=action_required` matches no runs and would silently
+# return an empty result — post-filter on `conclusion` instead.
head_sha=$(gh api "repos/<owner>/<repo>/pulls/<N>" --jq '.head.sha')
-pending=$(gh api
"repos/<owner>/<repo>/actions/runs?head_sha=${head_sha}&status=action_required&per_page=10"
\
- --jq '.workflow_runs | length')
+pending=$(gh api
"repos/<owner>/<repo>/actions/runs?head_sha=${head_sha}&per_page=20" \
+ --jq '[.workflow_runs[] | select(.conclusion == "action_required")] |
length')
if [ "$pending" -gt 0 ]; then
echo "refuse mark-ready: <N> has ${pending} workflow run(s) awaiting
approval at ${head_sha}" >&2
# Reclassify: this PR is really pending_workflow_approval, route accordingly.
@@ -242,9 +246,12 @@ mark-ready'ing without context.
```bash
# 1. Pre-check: refuse if any workflow run is awaiting approval (same as
mark-ready).
+# Runs awaiting approval surface as `status: "completed"` +
+# `conclusion: "action_required"` — `?status=action_required` matches
+# none of them, so post-filter on `conclusion`.
head_sha=$(gh api "repos/<owner>/<repo>/pulls/<N>" --jq '.head.sha')
-pending=$(gh api
"repos/<owner>/<repo>/actions/runs?head_sha=${head_sha}&status=action_required&per_page=10"
\
- --jq '.workflow_runs | length')
+pending=$(gh api
"repos/<owner>/<repo>/actions/runs?head_sha=${head_sha}&per_page=20" \
+ --jq '[.workflow_runs[] | select(.conclusion == "action_required")] |
length')
if [ "$pending" -gt 0 ]; then
echo "refuse mark-ready-with-ping: <N> has ${pending} workflow run(s)
awaiting approval at ${head_sha}" >&2
# Reclassify: this PR is really pending_workflow_approval.
@@ -438,12 +445,12 @@ protocol. Only after the maintainer confirms the diff
looks
non-malicious, approve:
```bash
-# List pending workflow runs for this PR
-gh api repos/<owner>/<repo>/actions/runs \
- -X GET \
- -f head_sha=<head_sha> \
- -f status=action_required \
- --jq '.workflow_runs[].id' |
+# List pending workflow runs for this PR.
+# Runs awaiting approval are returned as `status: "completed"` with
+# `conclusion: "action_required"` — `?status=action_required` matches
+# none of them. Post-filter on `conclusion` to enumerate the real set.
+gh api "repos/<owner>/<repo>/actions/runs?head_sha=<head_sha>&per_page=20" \
+ --jq '.workflow_runs[] | select(.conclusion == "action_required") | .id' |
while read run_id; do
gh api -X POST "repos/<owner>/<repo>/actions/runs/${run_id}/approve"
done
diff --git a/.claude/skills/pr-management-triage/fetch-and-batch.md
b/.claude/skills/pr-management-triage/fetch-and-batch.md
index c4d7f91..8f8735a 100644
--- a/.claude/skills/pr-management-triage/fetch-and-batch.md
+++ b/.claude/skills/pr-management-triage/fetch-and-batch.md
@@ -247,7 +247,8 @@ is the anti-pattern this file exists to prevent.
Before classification runs, fetch one REST call per page:
```bash
-gh api
"repos/<owner>/<repo>/actions/runs?event=pull_request&status=action_required&per_page=100"
+gh api "repos/<owner>/<repo>/actions/runs?event=pull_request&per_page=100" \
+ --jq '.workflow_runs[] | select(.conclusion == "action_required")'
```
This lists **every** workflow run across the repo that is
@@ -256,6 +257,16 @@ any PR on the current page whose head SHA appears in the
index
is `pending_workflow_approval` (see
[`classify-and-act.md#decision-table`](classify-and-act.md), row 1).
+**Why the post-filter, not `?status=action_required`.** The
+GitHub Actions API returns runs awaiting approval as
+`status: "completed"` with `conclusion: "action_required"` (the
+run has *finished* the queueing phase and is now blocked
+pending approval). The query parameter `?status=action_required`
+matches no runs in this state and silently returns an empty
+result, which lets `pending_workflow_approval` PRs slip through
+classification as `passing`. The post-filter on `conclusion`
+is the only correct way to enumerate them.
+
Why this is mandatory, not "fallback":
- `statusCheckRollup.state` aggregates only **completed**