feat(ci): auto-apply triage labels to issues - #3574
feat(ci): auto-apply triage labels to issues#3574Aleksei Sviridkin (lexfrei) wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe PR adds an issue-triage workflow and a Bats contract suite. The workflow classifies issues, applies triage labels, supports scheduled and manual sweeps, limits writes, handles refusals, and reports results. ChangesIssue triage automation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant IssueEvent
participant IssueTriageWorkflow
participant GitHubIssuesAPI
IssueEvent->>IssueTriageWorkflow: trigger issue, schedule, or dispatch
IssueTriageWorkflow->>GitHubIssuesAPI: retrieve open issues
GitHubIssuesAPI-->>IssueTriageWorkflow: return paginated issues
IssueTriageWorkflow->>IssueTriageWorkflow: classify issue and enforce write limits
IssueTriageWorkflow->>GitHubIssuesAPI: apply triage label
GitHubIssuesAPI-->>IssueTriageWorkflow: return write result or refusal
Possibly related issues
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
b81a204 to
11d63da
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/issue-triage.yaml:
- Around line 142-146: Update .github/workflows/issue-triage.yaml lines 142-146
to detect multiple triage labels and reconcile them to the documented
authoritative state instead of skipping every triage-labeled issue. Before the
event-driven addLabels call at lines 163-167, re-read and reclassify the issue;
likewise, before each sweep-item write at lines 203-220, re-read and reclassify
the issue so concurrent label changes cannot produce contradictory labels.
- Around line 189-194: Update the issue-processing flow around github.paginate
to use github.paginate.iterator with for-await page iteration, processing issues
incrementally and breaking once writes reaches MAX_WRITES_PER_RUN. Revise
deferred-count handling so it does not depend on fetching or retaining remaining
pages after the write cap is reached.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9c22e6e7-187f-47bc-ae6d-63c68acd84e8
📒 Files selected for processing (2)
.github/workflows/issue-triage.yamlhack/issue-triage-contract.bats
🚧 Files skipped from review as they are similar to previous changes (1)
- hack/issue-triage-contract.bats
11d63da to
e78bef9
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
## What this PR does The comment above the top-level `permissions:` block said jobs request the minimum extra scopes on top of it. That's backwards. A job-level block replaces the workflow-level one, and [every scope it does not name is set to `none`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions). Write `packages: write` alone in a job and it loses `contents: read` and can't check the repo out. Same wording in every file that carried the old line. It matches what #3574 uses for the workflow it adds. `backport.yaml` and `pull-requests.yaml` also appear in open #3569. Its changed lines there are the `cancel-in-progress` expressions two lines below the comment, and a three-way merge is clean in either order. Comment-only: every changed line starts with `#`, the files still parse, and actionlint reports the same 45 pre-existing findings before and after. ### Screenshots Not a UI change. ### Downstream repositories - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: Walked the trigger map against the diff. The only row that names a file here is the ccp one on `.github/workflows/tags.yaml`, and it triggers on release-prep behaviour, which this doesn't touch. ### Release note ```release-note docs(ci): the comment above the top-level `permissions` block in the workflow files now says a job-level block replaces it instead of adding to it ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified workflow permission guidance across automation processes. * Documented that the default token permissions are read-only. * Clarified that job-level permissions replace top-level defaults and must explicitly declare all required scopes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Issues created via the API or gh bypass the issue templates and arrive without any triage/* label; roughly half of the open issues carry none. Label issues on arrival (opened/reopened) and run a daily sweep as a backstop: issues already prioritised, assigned, epics, or frozen get triage/accepted, the rest triage/needs-triage. The sweep paces its writes and caps how many it makes per run, so a pass over a large backlog stays inside GitHub's secondary rate limits instead of failing partway through. The manual dispatch defaults to dry-run, so the one entry point a human can reach writes nothing until somebody unchecks the box. The schedule is the one event that writes without being asked. No PR lane can exercise this workflow, so the first real run is a sweep over every open issue. A contract test pins the executable lines that bound that: the pull-request skip, the already-triaged skip, the pacing and the cap, the dry-run default, the pinned action, and the token scopes. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
e78bef9 to
aee5c6c
Compare
What this PR does
Issues that arrive through the API or
gh issue createbypass the issue templates, so they carry notriage/*label and nothing in the repository adds one afterwards. In a snapshot of the open issues taken today, 138 of 277 have no triage label at all, which makes the triage queue impossible to filter on and hides how much of the backlog nobody has looked at yet.This adds a workflow that labels an issue when it is opened or reopened, plus a daily sweep that backstops labels removed by hand and works through the existing backlog. An issue somebody has assigned, or marked with one of
priority/critical-urgent,priority/important-soon,priority/important-longterm,epicorlifecycle/frozen, getstriage/accepted, since a maintainer has demonstrably looked at it; everything else getstriage/needs-triage. An issue already carrying anytriage/*label is left alone, so the sweep never overwrites a decision someone made by hand, and pull requests returned by the same listing endpoint are skipped. Against the snapshot above, the sweep appliestriage/acceptedto 43 issues andtriage/needs-triageto 95, and touches none of the 139 that are already triaged.The signal list is not simply every
priority/*tier, and the reason is worth stating.triage/acceptedis itself onstale.yaml'sexempt-issue-labels, so applying it does more than reset a clock: the issue stops being auto-closable. Every label in the signal list is already on that exempt list, so for those the exemption changes nothing.priority/backlogis the one tierstale.yamldeliberately leaves reapable, so it is not a signal here either and those issues go through triage like any other. An assignee is the single signal that is neither a label nor already exempt, so an issue whose only signal is an assignee does gain a permanent reprieve. That is the intended reading rather than a side effect: somebody took ownership, which makes it a known long-tail task rather than an abandoned one, and it is the same argumentstale.yaml's own comment makes for exemptingtriage/acceptedin the first place.Two operational details are worth calling out. First, the sweep paces its writes a second apart and caps a run at 400, because GitHub allows 80 content-generating requests a minute and 500 an hour, this client carries no throttling plugin and no retries, and a throttled write answers
403, which is exempt from retry anyway. Unpaced, the first pass over the backlog would not slow down when throttled; it would fail the job partway and leave the backlog half labeled. Whatever a run leaves behind stays unlabeled, so the next one picks it up. Second, adding a label bumps an issue'supdated_at, whichstale.yamlreads as activity, so the first sweep un-marks the issues currently labeledlifecycle/stale. That is deliberate and one-time: those issues get triaged now, and their staleness clock restarts from there.A write that fails does not take the rest of the run with it, but the two failure shapes are told apart rather than counted together. Any status other than a
403or429lets the run carry on, most often a404or410from an issue transferred or deleted mid-sweep, since a failure about one issue says nothing about the next. A403or429means the token lostissues: writeor the pacing stopped being enough, which applies to every write left in the run, so the sweep stops at the first one and the job fails loudly. Repeating a rejected request up to the cap is what gets an integration throttled harder.workflow_dispatchtakes adry_runinput that logs what would be labeled without writing anything. It is on by default, so a manual run writes nothing until somebody unchecks the box, andscheduleis the one event that writes without being asked. The cap applies in dry-run too, so a dry run predicts what a real one would do instead of printing a longer list nobody gets. The job asks forissues: writeand everything else is read-only, the action it uses is pinned by digest, and a 20-minute timeout keeps a hung call from holding the queue for the default six hours.No pull request lane can exercise this workflow, so its first real execution is a sweep over every open issue with nothing having run it before.
hack/issue-triage-contract.batspins the executable lines that bound that blast radius: the pull-request skip, the already-triaged skip, the pacing rate and the write cap, the failure handling, the digest-pinned action, the token scopes, the serialised queue, the cron ordering againststale.yaml, that both labels it writes exist in.github/labels.yml, and that no accepted-signal label sits outsidestale.yaml's exempt list. Each assertion was checked against a mutated copy of the workflow to confirm it goes red rather than merely existing.Suggested first step after merge: run the workflow by hand and read the summary before the 05:53 cron arms itself. The box arrives checked, so that run costs nothing.
Screenshots
Not a UI change, so there is nothing to show.
Downstream repositories
I walked the trigger map in
docs/agents/contributing.mdagainst the diff. The diff adds two files,.github/workflows/issue-triage.yamlandhack/issue-triage-contract.bats, and touches nothing else. No row matches: it adds and renames no package, changes no chart values, no schema, no version enum, noApplicationDefinition, no CRD, no namespace, no variant or bundle, no label or annotation any downstream repository matches on, no metric and no release asset. The map's onehack/row covershack/package.mkandhack/update-crd.sh, whichcozystack/ccpandcozystack/external-apps-examplecopy or anchor on; a new bats file next to them is neither.Release note
Summary by CodeRabbit
New Features
Tests