Skip to content

fix(planner): match runner labels case-insensitively#243

Merged
cbartz merged 11 commits into
mainfrom
fix/planner-case-insensitive-labels
Jun 19, 2026
Merged

fix(planner): match runner labels case-insensitively#243
cbartz merged 11 commits into
mainfrom
fix/planner-case-insensitive-labels

Conversation

@cbartz

@cbartz cbartz commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Makes planner flavor matching case-insensitive by storing labels in canonical lowercase:

  • stripDefaultLabels lowercases job labels and strips GitHub's implicit labels case-insensitively (fixes a Linux strip hole).
  • AddFlavor lowercases flavor labels on insert; isSelfHosted check is now case-insensitive.
  • Migration 0002 lowercases existing flavor and incomplete-job labels and re-runs flavor assignment.

Why we need it

GitHub injects OS/arch labels with arbitrary casing (e.g. X64, Linux). Matching used case-sensitive Postgres array containment, so a job requesting X64 never matched a flavor labelled x64. Those jobs ran (GitHub's own matching is case-insensitive) but contributed no pressure, so the autoscaler under-counted demand — a scaling-accuracy bug.

Test plan

TDD: failing unit + integration tests added first. Full ./internal/... suite passes with -tags=integration -race against PostgreSQL 16.14; gofmt/go vet clean.

Review focus / breaking changes

Labels are normalized at the write boundary instead of in hot-path SQL, leaving matching queries and the GIN index unchanged. Flavor labels are now stored (and returned) lowercase — safe since GitHub matching is case-insensitive. Migration 0002 is forward-only; its down migration deliberately raises a clear error because the normalization is irreversible.

Checklist

  • Changes comply with the project's coding standards and guidelines (see CONTRIBUTING.md and STYLE.md)
  • CONTRIBUTING.md has been updated upon changes to the contribution/development process — n/a
  • Technical author has been assigned to review the PR in case of documentation changes — only a changelog entry
  • I updated docs/changelog.md with user-relevant changes
  • I used AI to assist with preparing this PR
  • I added or updated tests as needed (unit and integration)
  • If integration test modules are used: I updated the workflow configuration — n/a
  • If this PR involves a Grafana dashboard: I added a screenshot of the dashboard — n/a
  • If this PR involves Terraform: terraform fmt passes and tflint reports no errors — n/a
  • If this PR involves Rockcraft: I updated the version — n/a

cbartz added 2 commits June 18, 2026 14:31
GitHub injects architecture/OS labels (e.g. "X64", "Linux") with casing
that differs from how flavors are defined, but flavor matching used
case-sensitive array containment. Jobs requesting "X64" never matched a
flavor labelled "x64", so they were left unassigned and contributed no
pressure, causing the autoscaler to under-count demand (the jobs still
ran on capacity provisioned for other flavors via GitHub's own
case-insensitive matching).

Normalize labels to lowercase as the canonical stored form: job labels
in stripDefaultLabels (which also fixes case-sensitive stripping of the
implicit "Linux" label) and flavor labels in AddFlavor. Make the
self-hosted eligibility check case-insensitive too.

Add migration 0002 to lowercase existing flavor and incomplete-job
labels and re-run flavor assignment, recovering the backlog of jobs left
unassigned purely because of label casing.

Copilot AI 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.

Pull request overview

This PR fixes a scaling-accuracy bug by making planner/database runner-label matching effectively case-insensitive via write-time normalization (lowercasing) for both jobs and flavors, plus a data migration to normalize existing rows and re-run flavor assignment for impacted in-flight jobs.

Changes:

  • Normalize job labels to lowercase during insertion and strip GitHub implicit labels case-insensitively.
  • Normalize flavor labels to lowercase during insertion to keep @> containment queries/indexes unchanged.
  • Add migration 0002 to lowercase existing labels and re-run assignment for previously-unmatched incomplete jobs; add unit + integration tests covering the new behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/planner/consumer.go Makes self-hosted eligibility check case-insensitive (but see review comment about Contains).
internal/planner/consumer_test.go Adds coverage for mixed-case Self-Hosted eligibility.
internal/database/database.go Lowercases job labels on insert via stripDefaultLabels; lowercases flavor labels on insert via lowercaseLabels.
internal/database/database_test.go Adds integration tests validating case-insensitive flavor matching and lowercase flavor label storage.
internal/database/labels_test.go Adds unit tests for stripDefaultLabels lowercasing + case-insensitive stripping.
internal/database/migrations/0002_lowercase_labels.up.sql Normalizes stored labels and re-runs assignment for incomplete jobs (but see review comment: needs to strip implicit labels too).
internal/database/migrations/0002_lowercase_labels.down.sql Documents irreversible normalization as a no-op down migration.
docs/changelog.md Documents the user-facing behavior change and migration.

Comment thread internal/database/migrations/0002_lowercase_labels.up.sql
Comment thread internal/planner/consumer.go
The 0002 migration lowercased existing incomplete-job labels but did not
strip GitHub's implicit labels (self-hosted, linux). Jobs that stored
uppercased implicit labels (e.g. "Linux") under the old case-sensitive
strip would keep them after lowercasing and still fail flavor matching,
so they were not recovered. Mirror ingestion: lowercase and strip the
implicit labels.

Copilot AI 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.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

cbartz added 2 commits June 19, 2026 08:24
golang-migrate runs up-only migrations fine, so the no-op
0002 down file added nothing. Also corrects the 2026 copyright
year on the files newly added in this PR.
Adds an integration test that seeds mixed-case flavor and job rows via
raw SQL (reproducing how the old case-sensitive code stored them) and
runs the exact embedded 0002 migration. Asserts flavor labels are
lowercased, incomplete jobs are lowercased/stripped and reassigned, and
completed jobs are left untouched as historical data. Closes the gap
where the SQL-only backfill had no automated coverage.

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread internal/database/migrations/0002_lowercase_labels.up.sql

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread internal/database/migrations/0002_lowercase_labels.up.sql Outdated
The job-label rewrite touched every incomplete job unconditionally.
Guard it like the flavor update so it only rewrites rows that have
uppercase characters or a still-present implicit label, avoiding
needless locks/WAL. Reassignment is a separate statement, so clean rows
are still picked up; covered by an added already-normalized test case.

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread docs/changelog.md Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

cbartz added 4 commits June 19, 2026 09:15
Restores a 0002 down file, but instead of a silent no-op it raises a
clear exception explaining the label normalization is irreversible. This
makes a down-direction run fail predictably with a meaningful reason
rather than an opaque file-not-found, while keeping the pairing
symmetric with 0001.
The job-label update intentionally normalizes all incomplete jobs, not
only unassigned ones: a later flavor disable/delete/update resets jobs to
unassigned and re-runs assignment, which re-reads j.labels, so assigned
jobs left mixed-case would re-introduce the casing mismatch. The previous
comment ('jobs still needing a runner') was misleading. Adds a test
covering an assigned, in-progress job.
@cbartz cbartz marked this pull request as ready for review June 19, 2026 10:19

@florentianayuwono florentianayuwono 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 thank you chris!

@cbartz cbartz enabled auto-merge (squash) June 19, 2026 10:55
@cbartz cbartz merged commit fac310f into main Jun 19, 2026
66 checks passed
@cbartz cbartz deleted the fix/planner-case-insensitive-labels branch June 19, 2026 11:07
cbartz added a commit that referenced this pull request Jun 25, 2026
* fix(cmd): count only owned jobs in waiting-p80-report

Restrict the P80 report to jobs we own by filtering on assigned_flavor IS
NOT NULL. The planner ingests webhook events for jobs served by runners we
don't manage (e.g. third-party self-hosted 'spread-enabled' runners on
repositories we receive webhooks from); those never match a configured
flavor, so including them skewed the reported waiting-time P80.

Document the pre-2026-06-19 caveat: the case-insensitive label matching fix
(#243) only backfilled in-progress jobs, so completed jobs left unassigned
due to label casing remain excluded for older date ranges.

* docs(cmd): refine waiting-p80-report wording per review

Soften the 'exactly when' claim about assigned_flavor (it can be NULL for
historical jobs and is cleared on flavor disable/delete), align the changelog
to the non-NULL filter, and replace Latin abbreviations per the docs style
guide.

* docs(cmd): fix waiting-p80-report test/README inconsistencies per review

Clarify the e2e seed comment so the 92 figure is clearly the P80 of the 5 base
jobs alone (the asserted day-1 P80 is 40 once the clamped negative-wait job
joins), and align the README caveat date to the 2026-06-18 changelog entry for
the case-insensitive label-matching change.

* docs(changelog): note pre-2026-06-18 NULL-flavor caveat for waiting-p80-report

Add the same caveat the README carries so a lower sample_count for older date
ranges isn't misread as data loss.

* docs: fix changelog spell-check failures in waiting-p80-report entry

Replace 'repos' with 'repositories' and drop 'undercount' (both flagged by the
docs spell check); mirror the wording in the command README for consistency.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants