Skip to content

fix(ci): harden the PR labeler's title parsing and map recurring scopes - #4666

Merged
IvanHunters merged 3 commits into
mainfrom
ci/pr-labeler-lookups
Oct 5, 2026
Merged

IvanHunters merged 3 commits into
mainfrom
ci/pr-labeler-lookups

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Fixes three problems in .github/workflows/pr-labeler.yaml. Each one either killed the job or put a PR in the wrong area, and none of them showed up anywhere.

  • Both maps are now looked up with Object.hasOwn. A title typed or scoped constructor, toString, hasOwnProperty or __proto__ used to resolve to an Object.prototype member and crash the step on l.startsWith, which left the PR with no labels at all.
  • Every leading [Backport ...] prefix is now stripped, not only the first one. With two prefixes, the second was read as a bracket scope, so the real scope and the kind/* label were both lost. The area/release label hid the problem by keeping area/uncategorized from firing.
  • Scopes that recur three or more times over the full PR history now map to an area. So does each area's own name (networking, storage, database, virtualization, testing, dependencies).

Scope-to-area decisions:

  • area/database: database, etcd-operator, opensearch, opensearch-operator
  • area/networking: networking, kubeovn, multus, kilo, hetzner-robotlb, ouroboros (it rewrites the CoreDNS Corefile to fix hairpin NAT, so it is networking, not platform)
  • area/virtualization: virtualization, vm, kubevirt-operator, kubevirt-cdi, kubevirt-instancetypes
  • area/platform: operator, cozystack-operator, controller, cozystack-controller, cozystack-basics, flux-shard-operator, migrations, scheduler, cozystack-scheduler, reloader, bootbox, and lineage. I put lineage here on purpose: no existing label covers it more precisely, and it is too narrow for an area of its own.
  • area/storage: storage, piraeus-operator, backupstrategy-controller
  • area/release: changelog, changelogs
  • area/ci: workflows, labeler
  • area/dependencies: dependencies, renovate
  • area/api: registry (the cozystack-api storage layer)
  • area/dashboard: dashboard-controller
  • area/monitoring: monitoring-agents
  • area/keycloak: keycloak-configure
  • area/testing: testing
  • New area/dx, added to .github/labels.yml: dx, hack, cozypkg. These are developer tooling (hack/ scripts, package Makefiles, cozypkg). area/build covers image builds and area/testing covers tests, and neither describes this work.

Left unmapped on purpose:

  • http-cache: the application was removed from the tree.
  • openbao: already mapped on main.
  • apps, system, docs: catch-all scopes that don't point to one area.
  • fix, bugfix: malformed titles.
  • security, rbac, governance, codeowners, maintenance, images, readme, telemetry, cluster-autoscaler: no single area fits any of them.

I did not add spelling normalisation. kubeovn got its own row, since it is the only alternate spelling that recurs.

A new contract test, hack/pr-labeler-contract.bats, pulls the workflow's own script: block out of the file and runs it under node with stubbed context, core and github, so it tests the shipped code, not a copy. On main it is red on the prototype titles (THROW: l.startsWith is not a function), on the double backport (only area/release), on the newly mapped scopes and on the area-name scopes. It also checks that every map target exists in labels.yml and that no key is written twice. The suite fails rather than skips when node is missing. The older node-based suites skip, so this is the first one that needs node on the unit-test runner.

Some things stay out of this PR. The scope table in docs/agents/contributing.md is a summary. It has no row for area/dx and does not mention that an area's own name works as a scope. Separately, the singular migration still maps to area/virtualization as it did before, while the new migrations maps to area/platform.

A backport PR whose scope is unmapped still never gets area/uncategorized, even with a single prefix. The area/release added by the strip satisfies the fallback check. This behaviour is the same on main and is not changed here.

The test that checks map targets against labels.yml passes by itself if the map literal cannot be extracted. The duplicate-key and area-name tests go red in that case, so the suite as a whole still fails.

Closes #4125, closes #4124, closes #4122

Screenshots

Not a UI change.

Downstream repositories

Release note

fix(ci): the PR labeler no longer crashes on titles scoped like Object.prototype members, handles nested backport prefixes, and maps more recurring scopes, including a new area/dx.

Summary by CodeRabbit

  • New Features
    • Added the area/dx label for issues and pull requests related to developer tooling.
  • Improvements
    • Pull requests can now be categorized using a broader set of scope aliases.
    • Repeated leading backport prefixes are recognized and route the pull request to the release area.
    • Label mapping now avoids treating inherited property names as valid mappings.

Both maps are plain object literals, so a title typed or scoped
`constructor`, `toString` or `__proto__` resolved to an
Object.prototype member. That value is truthy, landed in the label
set and killed the step on `l.startsWith`, leaving the PR with no
labels at all.

The new contract test runs the workflow's own script under node with
stubbed context, so it exercises the code as shipped rather than a
copy of it.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
A backport of a backport is titled with two `[Backport ...]` prefixes.
Only the first was stripped, so the second was parsed as a bracket
scope: the real scope and the kind were lost, and the area/release
added by the strip suppressed the area/uncategorized fallback, so the
PR looked labelled correctly.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Over the full PR history a set of scopes recurring three or more
times still fell through to area/uncategorized. Most of them have an
obvious home and were only missing a row. Subsystem names such as
`networking` or `storage` are now accepted as the scope of their own
area, since contributors reach for those as often as for a package
name.

`dx`, `hack` and `cozypkg` describe developer tooling, which no
existing area covers: area/build is image builds, area/testing is
tests. They get a new area/dx rather than being filed under either.

The contract test also pins that every map target exists in
labels.yml, since the label sync workflow creates only what that file
defines, and that no key is written twice, since a repeated key
silently overrides the earlier one.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@github-actions github-actions Bot added area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files labels Oct 1, 2026
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 496d7a06-f2ca-423c-ac12-739a8456c520

📥 Commits

Reviewing files that changed from the base of the PR and between bc400df and 30689f8.

📒 Files selected for processing (3)
  • .github/labels.yml
  • .github/workflows/pr-labeler.yaml
  • hack/pr-labeler-contract.bats

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The pull request adds area labels and scope mappings, updates backport-title parsing and mapping lookups in the PR labeler, and adds a Bats contract suite for the workflow script.

Changes

Pull request labeling

Layer / File(s) Summary
Area labels and scope mappings
.github/labels.yml, .github/workflows/pr-labeler.yaml, hack/pr-labeler-contract.bats
Adds the area/dx label and scope aliases across multiple areas. Contract checks cover area-label targets, representative mappings, and duplicate scope keys.
Title parsing and mapping lookups
.github/workflows/pr-labeler.yaml, hack/pr-labeler-contract.bats
Strips repeated leading backport prefixes and checks kind and area mappings with own-property lookups. The Bats suite executes the workflow script with stubbed context and APIs and tests backport and prototype-name titles.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 30689

The change broadens scope labeling and hardens title parsing, with contract tests covering the changed behavior. No actionable merge-blocking risk is established; merge after normal checks pass.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main workflow changes: hardened title parsing and added mappings for recurring scopes. It is concise and specific.
Linked Issues check ✅ Passed The PR meets the coding requirements in [#4125], [#4124], and [#4122]. The labeler uses Object.hasOwn for both typeToKind and scopeToArea. The parser removes every leading [Backport ...] prefi…
Out of Scope Changes check ✅ Passed The changes remain within the linked issue scope. The added mappings, area/dx label, and contract tests directly support labeler parsing, recurring-scope categorization, target consistency, and regr…
Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Checked the parsing hardening by running the workflow's own github-script block under node, at the merge base and at this head, over the 60 most recent real PR titles plus an adversarial set. Contract suite is green (8/8).

What the diff actually changes in behaviour, confirmed by diffing base against head output:

  • Double [Backport ...] prefixes now parse. Before, [Backport 1.2] [Backport 1.3] fix(linstor): x kept only area/release and dropped both the scope and the kind. Now it yields area/release, area/storage, kind/bug.
  • A scope or type named after an Object.prototype member no longer kills the job. fix(constructor): x threw l.startsWith is not a function at the base; Object.hasOwn turns that into area/uncategorized, kind/bug.
  • Everything else is byte-identical. type(scope): x, type!: x, type(scope)!: x, composite fix(a, b): x, and [scope] bracket titles all produce the same labels they did before. On the 60 real titles the only three differences were titles that used to land in area/uncategorized and now resolve correctly: controller to platform, workflows to ci, monitoring-agents to monitoring.

The scope map is clean. 130 keys, no duplicates, and every one of the 22 target areas is defined in labels.yml. I spot-checked the less obvious additions against the package tree: ouroboros ships etchosts/coredns/networkpolicy, so networking is right, and dashboard-controller is a real scope from the changelogs. The tests run the extracted script rather than a copy of it, which is the right call.

Two non-blocking notes, neither worth a change:

  • [NIT] Adjacent backport prefixes with no space between them ([Backport a][Backport b] ...) still fall to area/uncategorized, because the strip regex wants whitespace after each ]. This is pre-existing, and tooling-generated backports always put the space in.
  • [NIT] The contract does not pin the breaking-change forms or composite-scope parsing. The PR does not touch those paths and I verified they still hold, so it is optional coverage.

Merged current origin/main into the head before signing off; the contract suite stays green there too.

@IvanHunters
IvanHunters enabled auto-merge October 5, 2026 11:06
@IvanHunters
IvanHunters merged commit 6e4c9c4 into main Oct 5, 2026
31 of 32 checks passed
@IvanHunters
IvanHunters deleted the ci/pr-labeler-lookups branch October 5, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci Issues or PRs related to CI workflows, GitHub Actions, automation kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

2 participants