Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add pr-triage skill — maintainer-driven first-pass PR triage - #65648

Merged
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill
Apr 25, 2026
Merged

Add pr-triage skill — maintainer-driven first-pass PR triage#65648
potiuk merged 9 commits into
apache:mainfrom
potiuk:add-pr-triage-skill

Conversation

@potiuk

@potiukpotiuk commented Apr 22, 2026

Copy link
Copy Markdown
Member

Adds a new Claude Code skill, pr-triage, that replaces the triage mode of breeze pr auto-triage with a CLI-driven, maintainer-in-the-loop flow for first-pass triage of open PRs.

Scope is triage only — no LLM code review, no approve/request-changes, no merging, no TUI. A future companion skill can cover the review side; keeping them separate keeps this one fast, rate-limit-safe, and easy to audit.

Shape

Follows the existing airflow-translations convention:

  • .github/skills/pr-triage/ holds the canonical content — SKILL.md as entry point plus focused topic files (prerequisites.md, fetch-and-batch.md, classify.md, suggested-actions.md, actions.md, comment-templates.md, workflow-approval.md, interaction-loop.md, stale-sweeps.md).
  • .claude/skills/pr-triage is a committed symlink to .github/skills/pr-triage so Claude Code picks the skill up without per-developer setup.
  • .gitignore is updated to let the symlink through while keeping the rest of .claude/ local.
  • .pre-commit-config.yaml adds pr-triage/SKILL.md to the agentic-markdown license-header exclusion (mirroring airflow-translations/SKILL.md) so the YAML frontmatter can stay at the top of the file.

Efficiency guarantees documented throughout

  • One aliased GraphQL call returns a page of 20 PRs with rollup state, mergeable, unresolved threads, latest reviews, and the last N comments — no per-PR gh pr view calls.
  • The next page is prefetched in parallel with the maintainer's current decision.
  • Classified PRs are presented in action-grouped batches with [A]ll / [E]ach / [P]NN / [O]verride / [S]kip keys so similar decisions compress to one keystroke; close groups still require per-PR confirm inside [A].
  • A session cache keyed by (pr_number, head_sha) at /tmp/pr-triage-cache-<repo-slug>.json skips already-handled PRs on re-invocation.
  • Optimistic-lock SHA re-check before every mutation catches contributor pushes mid-session.

Refinements from running the skill end-to-end on apache/airflow

Six follow-up commits came out of driving the skill through the full backlog (~150 actions executed), plus the latest defensive guard against a recurrence of the workflow-approval bug:

  • pr-triage: never suggest rebase on CONFLICTING PRs — route to draft — GitHub's update-branch endpoint returns 422 on every CONFLICTING PR; the rebase suggestion was wasting round-trips. Now routed straight to draft with the merge-conflicts violation.
  • pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks — the 3-week floor was leaving queue pressure without adding useful signal.
  • pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body — Copilot review comments sitting a full week already signal a stalled PR. The violation body was rewritten to say "some Copilot suggestions may be incorrect — that is expected, but the author is still responsible for responding: apply, reply explaining why it does not apply, or resolve".
  • pr-triage: make action_required REST check primary for pending_workflow_approvalstatusCheckRollup.state can report SUCCESS for a first-time-contributor PR where every real CI workflow is stuck in action_required, because the rollup aggregates only completed check-runs and fast bot checks (Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally before the real CI is allowed to start. Classification now requires the repo-level REST call GET /repos/.../actions/runs?status=action_required as the primary signal.
  • pr-triage: spell out author responsibility to mark threads resolved — the Unresolved review comments and Unaddressed Copilot review violation bodies now explicitly state that the author must click "Resolve conversation" themselves once a thread has been addressed.
  • pr-triage: mandatory pre-check against action_required on mark-ready — classification-time guard was not enough: even after the detection fix, the mark-ready action could still fire on a PR with pending approvals if the batch fetch was taken before the action_required runs were indexed, or if the 30-context sample missed the real-CI entries. The mark-ready action now has a mandatory REST pre-check and refuses the mutation when any workflow run is awaiting approval at the PR's head SHA, reclassifying the PR as pending_workflow_approval instead. Promoted to Golden Rule 1b in SKILL.md so implementors cannot miss it.

Also refreshes the breeze auto-triage help image

dev/breeze/doc/images/output_pr_auto-triage.{svg,txt} were stale on main (boring-cyborg.yml added labels the committed image hadn't regenerated yet). The pre-commit hook regenerates it on every touch of the breeze config, so it lands here. Loosely related — both about PR triage.

Test plan

  • Invoked the skill on apache/airflow with oldest-updated sort — swept 15+ pages, took ~180 actions across two sittings (mark-ready, drafts, closes, pings, comments, CI reruns, workflow approvals).
  • Every mid-session refinement above was applied and then re-verified against the same backlog to confirm the new classification behaviour.
  • The mark-ready pre-check was added after discovering two separate sessions where statusCheckRollup.state == SUCCESS PRs were marked ready while workflow approval was still pending. The guard is executable (not just documented) and refuses the mutation deterministically.
  • prek static checks pass (prek run --from-ref main --stage pre-commit — markdownlint, license headers, boring-cyborg, breeze-docs all green).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Opus 4.7 (1M context)

Generated-by: Claude Opus 4.7 (1M context) following the guidelines

@o-nikolas

Copy link
Copy Markdown
Contributor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

@potiukpotiuk changed the title Add pr-triage skill (maintainer PR queue sweep via Claude Code)Add pr-triage skill — maintainer-driven first-pass PR triageApr 23, 2026
@potiuk

Copy link
Copy Markdown
MemberAuthor

This is a TONNE of content to review, I'm slowly working through it (at human pace 😓), but it looks great so far ❤️

I am also iterating on it while triaging remaining PRs.

A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
…weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
…ow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
@potiuk
potiukforce-pushed the add-pr-triage-skill branch from 7e9028a to ba4f41dCompareApril 24, 2026 07:46
@potiuk

Copy link
Copy Markdown
MemberAuthor

Updated - with feedback to "AI assisted" message. Once merged I will update existing comments - to also have links to the newly added section.

@jscheffljscheffl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a longer read. Looks good and I think we can merge and then incrementally adjust as we might learn along the path.

@choo121600choo121600 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is nice 👍
logic is quite detailed, so I spent some time reviewing it.
Mostly looks good to me.

@potiuk
potiuk merged commit 0ed3e73 into apache:mainApr 25, 2026
142 checks passed
@potiuk
potiuk deleted the add-pr-triage-skill branch April 25, 2026 18:23
@potiuk

Copy link
Copy Markdown
MemberAuthor

Thanks! Yeah. the nice thing is that anyone can update the logic as long as they know English :)

@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-2-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

StatusBranchResult
v3-2-testPR Link

github-actionsBot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Apr 25, 2026
…age (apache#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
potiuk added a commit that referenced this pull request Apr 26, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request Apr 27, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 pushed a commit that referenced this pull request May 20, 2026
…age (#65648) (#65848)
* Add pr-triage skill for maintainer-driven PR queue sweep
A skill-based replacement for the triage mode of breeze pr auto-triage.
Scope is triage only — no LLM code review, no approve/request-changes,
no merging, no TUI.
Shape follows the airflow-translations convention:
.github/skills/pr-triage/ holds the canonical content;
.claude/skills/pr-triage is a committed symlink so Claude Code picks
it up without per-developer setup. .gitignore is updated to let the
symlink through, and the agentic-markdown license hook excludes
SKILL.md entry point so YAML frontmatter can stay at the top.
Also refreshes dev/breeze/doc/images/output_pr_auto-triage.{svg,txt}
— upstream's committed image was stale (boring-cyborg.yml added
labels the committed image hadn't regenerated yet), and the pre-commit
hook regenerates it on every touch of the breeze config.
Generated-by: Claude Opus 4.7 (1M context) following the guidelines
in contributing-docs/05_pull_requests.rst
* pr-triage: add Copilot-review staleness + prefer author-pings
Two rule refinements surfaced while running the skill on
apache/airflow:
1. New classification stale_copilot_review. When a PR has an
unresolved review thread authored by a Copilot-review bot
(copilot-pull-request-reviewer[bot] and siblings) and the
comment is >= 14 days old with no author reply, the skill
classifies it as stale_copilot_review and suggests draft
with a new "Unaddressed Copilot review" violation. The
14-day window is deliberately longer than the 24h/96h CI
grace — review feedback takes longer to address and a
two-day nudge would be noisy.
2. Strongly prefer pinging the PR author over the reviewer on
the ping action. Both comment families (review-nudge for
stale CHANGES_REQUESTED, reviewer-ping for unresolved
threads) now document an author-primary default body and a
reviewer-re-review variant. The reviewer-re-review body is
only used after an explicit inspection of the post-review
diff + in-thread replies confirms the feedback has been
addressed in code or explained away. The previous logic
(which switched on whether the author had @-mentioned the
reviewer) put the re-review request on the reviewer's desk
even when the actual work was still on the author's side.
Updates classify.md, suggested-actions.md, actions.md,
comment-templates.md, SKILL.md, and interaction-loop.md so the
group ordering, data requirements, and body-template decision
are consistent across the skill.
* pr-triage: never suggest rebase on CONFLICTING PRs — route to draft
Empirically every rebase attempted on a PR whose mergeable state
is CONFLICTING came back with "Cannot update PR branch due to
conflicts". GitHub's update-branch endpoint does a side-merge
of the base branch into the PR head; when the merge doesn't
apply cleanly, it refuses, and retrying burns round-trips
without progress. The fix is to stop suggesting rebase in that
state at all — those PRs go straight to draft with the
merge-conflicts violation so the author is pointed at the
local-rebase instructions in the comment body.
- suggested-actions.md: the "mergeable == CONFLICTING" rule now
maps to draft regardless of other signals. An explicit hard
rule spells out why.
- actions.md#rebase: a mergeable check runs before the
update-branch call; if the state is CONFLICTING the action
refuses with a route-to-draft hint. If a non-CONFLICTING call
still 422s, the skill does not retry — it falls through to
draft.
- SKILL.md Step 3 table updated to reflect the new mapping.
* pr-triage: tighten untriaged-draft stale threshold from 3 weeks to 2 weeks
Drafts that have had no activity for 14 days (was 21) now fall into
Sweep 1b (untriaged-draft close). The triaged-draft 7-day threshold
for Sweep 1a is unchanged.
Maintainer preference: two weeks is enough signal that a draft has
stalled; waiting the extra week was leaving queue pressure without
adding useful context on author intent.
* pr-triage: stale-Copilot threshold 14 days -> 7 days, soften body
Two related changes for the stale_copilot_review classification:
- Tighten the trigger threshold. Copilot reviews that sit unaddressed
for a week already signal a stalled PR — waiting two weeks before
drafting adds queue pressure without useful signal.
- Revise the draft-comment language to explicitly call out that some
Copilot suggestions may be incorrect or irrelevant, and that it is
still the author's responsibility to respond — apply the fix, reply
in-thread explaining why it does not apply, or resolve the thread
if the feedback is no longer relevant. Previously the wording could
be read as 'please do everything Copilot said', which is the wrong
bar.
* pr-triage: make action_required REST check primary for pending_workflow_approval
A PR that has real CI runs held in action_required for approval
can still report statusCheckRollup.state == SUCCESS, because the
rollup aggregates only completed check-runs and fast bot checks
(Mergeable, WIP, DCO, boring-cyborg) succeed unconditionally
before the real CI is even allowed to start. Trusting the rollup
classifies such a PR as passing and leads to premature mark-ready.
Changes:
- classify.md: rewrite C1 (pending_workflow_approval) so the
repo-level REST call GET /repos/.../actions/runs?status=action_required
is the primary signal, not a fallback. Index the response by
head_sha once per page. Call out that the rollup-based check
alone is not sufficient and explain why.
- classify.md: rewrite 'Verifying real CI ran' from an optional
note into a mandatory pre-passing guard. Expand the real-CI
pattern list to include Tests (exact), CodeQL, and
'Check newsfragment PR number'.
- fetch-and-batch.md: add an 'Mandatory: action_required run
index per page' section that documents the REST call as part of
the per-page fetch plan (one extra round-trip per page, well
inside the budget).
* pr-triage: spell out author responsibility to mark threads resolved
The Unresolved review comments / Unaddressed Copilot review violation
bodies now explicitly state that it is the author's responsibility to
mark a thread as resolved once they believe it has been addressed —
whether that was by pushing a fix or by replying with an explanation
of why the suggestion doesn't apply.
Reviewers do not auto-close their own threads, so a thread that is
addressed but left unresolved reads as 'still waiting on the author'
and blocks the PR from moving forward. Making this explicit in the
draft comment is the difference between first-time contributors
wondering why their PR still looks blocked after a fix vs. knowing
to click 'Resolve conversation' at the bottom of each thread.
* pr-triage: mandatory pre-check against action_required on mark-ready
Sole purpose of this commit is to add an executable guard in
actions.md that the classifier's real-CI-ran check cannot
substitute for. Empirically, the classifier can still green-light
a PR that has pending workflow approvals if the GraphQL batch
fetch was taken before GitHub indexed a freshly-required workflow
run, or if only the bot subset of checks has completed and the
classifier's 30-context sample missed a real-CI entry.
Changes:
- actions.md §mark-ready: add a MUST-DO pre-mutation check —
GET /repos/<owner>/<repo>/actions/runs?head_sha=<SHA>&status=
action_required returning any result is a hard refuse. On
refuse, reclassify the PR as pending_workflow_approval and
route accordingly instead of silently dropping the mutation.
- SKILL.md Golden rules: add rule 1b elevating the check to the
top-of-file contract so a skill implementor can't miss it.
The rationale (fast bot checks finish first while Tests /
CodeQL / newsfragment sit in action_required) is spelled out
so nobody thinks this is belt-and-braces redundancy.
* pr-triage: add AI-attribution footer to contributor comments
Every contributor-facing triage comment now ends with a short
footer that (a) notes the comment was drafted by an AI-assisted
tool and may contain mistakes, (b) reassures the contributor
that a human maintainer will take the next look once they
address the feedback, and (c) links to a new section in the
maintainer-triage contributing doc that explains why the first
pass is automated — to free scarce maintainer time for the
conversation with contributors rather than mechanical queue
sweeping.
The footer is defined once in comment-templates.md as
<ai_attribution_footer> and referenced from every
contributor-facing template (draft, comment-only, close,
review-nudge, reviewer-ping, stale-draft-close,
inactive-to-draft, stale-workflow-approval). The
suspicious-changes template is intentionally excluded — it is
terse and already directs the contributor to maintainers on
Slack, and adding the footer would dilute the signal.
SKILL.md carries a new Golden rule 8 capturing the requirement
and the link to the rationale anchor.
(cherry picked from commit 0ed3e73)
Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@potiuk@o-nikolas@choo121600@jscheffl