Skip to content

feat: surface pull requests that need action - #9

Merged
fredclausen merged 4 commits into
mainfrom
feat/pr-check-state
Aug 10, 2026
Merged

feat: surface pull requests that need action#9
fredclausen merged 4 commits into
mainfrom
feat/pr-check-state

Conversation

@fredclausen

@fredclausen fredclausen commented Aug 10, 2026

Copy link
Copy Markdown
Member

Default-branch health said nothing about whether an open PR is stuck. Renovate PRs sitting green because automerge is off, and PRs that are flat broken, were both invisible.

New metrics

Metric Meaning
github_pull_needs_attention Non-draft PR failing checks, conflicting, or green and awaiting a manual merge
github_pull_ready_to_merge Checks pass, mergeable, no auto-merge armed — waiting on a button press

Plus checks, mergeable, and auto_merge labels on the existing PR metric.

checks is the head commit's statusCheckRollup. Unlike on a default-branch commit (null 30/31 times), it is reliable here — PR-triggered workflows attach to the head commit by construction. Measured 7/10 populated; the 3 nulls are repos with no PR CI, reported as checks="none" rather than stuck pending.

mergeable="unknown" is not treated as mergeable. GitHub computes it lazily, so a first query often returns UNKNOWN for a PR that is actually conflicting — two sampled PRs both resolved to CONFLICTING on re-query. Treating it as mergeable would report conflicted PRs as ready to merge.

Also: a default-branch false positive

A run's event must still be a trigger the workflow currently declares.

frext and bike-fitter-1000 both reported failing push runs for a ci.yml that declares only pull_request. The workflow used to run on push; GitHub keeps those runs forever. Confirmed: since the last ci.yml change, frext produced only pull_request runs.

The trigger list is parsed from the workflow file during the existing cron lookup, so it costs no additional request. An unparsable file leaves the list empty, which accepts any event — a failed lookup degrades to previous behaviour rather than blanking a repository.

Verification against live data

Each of the 16 previously-reported failures was individually re-checked against its workflow's declared triggers — all 16 KEPT, nothing over-filtered. The count fell to 1 because the update-flakes failures were genuinely fixed upstream in the meantime, not because they were hidden.

Currently surfaced as needing attention:

freminal            #481  checks=failure  mergeable    auto=true   <- broken
github-ci-exporter  #8    checks=failure  mergeable    auto=false  <- broken
plane-alert-db      #854  checks=success  mergeable    auto=false  <- awaiting merge
nixos               #1615 checks=success  conflicting  auto=false
gitbook-adsb-guide  #176  checks=success  conflicting  auto=false
gitbook-adsb-guide  #185  checks=success  conflicting  auto=false
docker-vesselalert  #32   checks=none     conflicting  auto=false

Testing

93 tests (6 new for triggers, 9 for PR state), covering: automerge on vs off, conflicting-but-green, UNKNOWN mergeability, drafts, repos with no PR CI, all three YAML on: forms, and the exact frext workflow shape.

cargo xtask ci green; all 28 pre-commit hooks pass.

Summary by CodeRabbit

  • New Features

    • Added pull-request metrics for check status, mergeability, auto-merge status, attention required, and readiness to merge.
    • Added clearer pull-request classification for conflicts, pending or missing checks, drafts, and unknown states.
    • Workflows now recognize declared trigger events when evaluating runs.
  • Bug Fixes

    • Prevented runs from being reported when their events are no longer supported by the workflow.
    • Improved workflow metadata and schedule handling.
  • Documentation

    • Updated README documentation for new metrics, event validation, disabled workflows, and pull-request state classification.

Default-branch health said nothing about whether an open PR was stuck.
Renovate PRs sitting green because automerge is off on that repo, and
PRs that are flat broken, were both invisible.

Adds `github_pull_needs_attention` and `github_pull_ready_to_merge`,
plus `checks`, `mergeable`, and `auto_merge` labels on the existing PR
metric. `checks` is the head commit's statusCheckRollup, which unlike on
a default-branch commit is reliable: PR-triggered workflows attach to the
head commit by construction.

`mergeable="unknown"` is deliberately not treated as mergeable. GitHub
computes mergeability lazily, so a first query often returns UNKNOWN for
a PR that is in fact conflicting -- two PRs sampled here both resolved to
CONFLICTING on re-query. Treating UNKNOWN as mergeable would report
conflicted PRs as ready to merge.

Also fixes a false positive in default-branch CI state: a run's event
must still be a trigger the workflow currently declares. frext and
bike-fitter-1000 both reported failing `push` runs for a ci.yml that
declares only pull_request; the workflow used to run on push, and GitHub
keeps those runs forever. The trigger list is parsed from the workflow
file during the existing cron lookup, so it costs no additional request,
and an unparsable file leaves the list empty which accepts any event --
a failed lookup degrades to the previous behaviour rather than blanking a
repository.

Verified against the live API: all 16 previously-reported failures were
individually confirmed as still matching a declared trigger, so nothing
was over-filtered. frext and bike-fitter-1000 now report their real
pull_request state.
Copilot AI lite review requested due to automatic review settings August 10, 2026 17:41
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2294be86-8a77-4eea-8305-054b3dd3db24

📥 Commits

Reviewing files that changed from the base of the PR and between 7e959e2 and 5366645.

📒 Files selected for processing (1)
  • github-ci-exporter/src/collector.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • github-ci-exporter/src/collector.rs

📝 Walkthrough

Walkthrough

The exporter validates workflow runs against declared workflow events and exposes pull-request checks, mergeability, auto-merge, attention, and readiness metrics. Workflow definitions provide trigger and cron metadata, while interval caching uses workflow file paths.

Changes

Pull metrics and workflow validation

Layer / File(s) Summary
Pull request state contracts and GraphQL mapping
github-ci-exporter/src/model.rs, github-ci-exporter/src/github/graphql.rs
GraphQL pull-request data maps to check and mergeability states. OpenPull classifies pull requests that need attention or are ready to merge.
Pull request metric export
github-ci-exporter/src/metrics.rs, github-ci-exporter/src/collector.rs, README.md
Pull-request metrics add state labels and publish attention and readiness gauges. Repository series clearing includes the new metric families.
Workflow definition resolution and event filtering
github-ci-exporter/src/github/rest.rs, github-ci-exporter/src/collector.rs, README.md
Workflow definitions parse triggers and cron schedules. Run reduction excludes events not declared by the workflow. Tests cover YAML forms, malformed input, fingerprints, and filtering.
Path-based workflow interval caching
github-ci-exporter/src/collector.rs
The interval cache uses workflow paths, refreshes current definitions, retains prior triggers after failed lookups, and removes deleted workflows.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubGraphQL
  participant OpenPull
  participant Collector
  participant PrometheusMetrics
  GitHubGraphQL->>OpenPull: provide pull request state
  OpenPull->>Collector: provide labels and classifications
  Collector->>PrometheusMetrics: publish pull request metrics
Loading
sequenceDiagram
  participant Collector
  participant GitHubREST
  participant WorkflowDefinition
  participant RunReducer
  Collector->>GitHubREST: fetch workflow definition
  GitHubREST->>WorkflowDefinition: parse triggers and cron schedules
  WorkflowDefinition->>Collector: return workflow metadata
  Collector->>RunReducer: provide declared triggers
  RunReducer->>Collector: retain valid workflow runs
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding metrics for pull requests that need action.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pr-check-state

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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-ci-exporter/src/github/rest.rs`:
- Around line 321-329: Update fingerprint_workflows to include Workflow.triggers
in the cache fingerprint, sorting the triggers deterministically before hashing
so trigger-only changes produce a new key. Add a regression test demonstrating
that changing only the workflow triggers changes the fingerprint.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c9be23e-55c0-437d-8aa2-32bf1abe4e7a

📥 Commits

Reviewing files that changed from the base of the PR and between 656b055 and d331249.

📒 Files selected for processing (6)
  • README.md
  • github-ci-exporter/src/collector.rs
  • github-ci-exporter/src/github/graphql.rs
  • github-ci-exporter/src/github/rest.rs
  • github-ci-exporter/src/metrics.rs
  • github-ci-exporter/src/model.rs

Comment thread github-ci-exporter/src/github/rest.rs

Copilot AI left a comment

Copy link
Copy Markdown

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 improves observability of “stuck” open pull requests by exporting additional PR-state labels and introducing two new PR actionability metrics, and it reduces default-branch CI false positives by filtering workflow runs to only events still declared by the current workflow triggers.

Changes:

  • Add PR-level state extraction via GraphQL (checks, mergeable, auto_merge) and export them as labels on existing PR metrics.
  • Introduce github_pull_needs_attention and github_pull_ready_to_merge gauges for actionable PR states.
  • Parse workflow on: triggers from workflow YAML and discard historical runs whose events are no longer declared, preventing false positives after trigger changes.

Reviewed changes

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

Show a summary per file
File Description
README.md Documents the new PR actionability metrics and new PR metric labels; documents the new “event must still be declared” workflow-run filtering rule.
github-ci-exporter/src/model.rs Adds ChecksState and MergeableState enums to model PR check-rollup and mergeability states.
github-ci-exporter/src/metrics.rs Extends PR label set and registers new pull_needs_attention / pull_ready_to_merge metric families.
github-ci-exporter/src/github/rest.rs Adds workflow trigger storage, parses triggers from workflow YAML, and filters runs by currently-declared triggers.
github-ci-exporter/src/github/graphql.rs Extends PR GraphQL query to fetch check rollup, mergeability, and auto-merge, and computes actionability helpers.
github-ci-exporter/src/collector.rs Populates new PR labels/metrics and resolves workflow definitions (crons + triggers) for run filtering.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread github-ci-exporter/src/collector.rs Outdated
Comment on lines +310 to +312
let intervals = resolve_definitions(client, &repo, &mut workflows).await;
cache.workflows.insert(key.clone(), workflows);
cache.intervals.insert(key, intervals);
Comment thread github-ci-exporter/src/github/rest.rs Outdated
Comment on lines +101 to +103
/// `merge_group` accepts `push` too: a merge-queue run reports
/// `event = "merge_group"`, but the queue exists to gate what lands on the
/// branch, so treating it as branch state is correct.
reduce_runs now filters on the trigger list, which makes triggers part of
the reduction's input. The fingerprint covered only path and name, so a
trigger-only edit kept the same cache key: the runs listing is unchanged
by such an edit, the request answers 304, and the stale reduction
computed against the old trigger set is replayed. That is the same
stale-reduction bug the fingerprint was introduced to prevent,
reintroduced by the trigger filter.

Triggers are sorted before hashing so a reordering of the `on:` mapping
cannot spuriously invalidate every entry and force a full uncached sweep.

Verified by reverting the fix: two of the three new tests fail without
it. An earlier attempt at that verification used a revert whose string
did not match, so the tests passed and appeared to prove nothing --
worth stating because "the test passes with the fix removed" is the only
signal that distinguishes a real regression guard from a decorative one.
Two review findings, both confirmed:

- `cache.intervals.insert` replaced the whole per-repo interval map every
  cycle. `resolve_definitions` omits any workflow whose contents fetch
  failed, so a transient error dropped previously-known intervals,
  making workflow_expected_interval_seconds disappear and reappear. That
  flaps GitHubScheduledWorkflowStale, which compares against it. Now
  merged, with entries for workflows that no longer exist retained-then-
  pruned so a deleted cron workflow still stops being expected to run.

- The same failure emptied the trigger list, and an empty list means
  "accept any event", so one bad cycle would silently re-admit runs from
  a superseded trigger -- the frext/bike-fitter false positive returning
  intermittently. Triggers are now carried forward from the previous
  cycle, matched on file path so a rename keeps them, while a freshly
  resolved list always wins over the cached one.

Also removes a false claim from `declares`: the doc comment described a
merge_group/push equivalence that the code did not implement. It should
not: merge-queue runs execute on a gh-readonly-queue branch, so the runs
request's `branch=` filter excludes them before reduction. Verified
against nixos and freminal -- 24 and 6 merge_group runs respectively,
none under `branch=main`. The comment now records why the case cannot
arise instead of describing behaviour that does not exist.

Both fixes are extracted into pure functions so they are testable.
Verified by reverting: 4 of the 7 new tests fail without them.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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-ci-exporter/src/collector.rs`:
- Around line 357-365: Update merge_intervals and its callers to retain cached
intervals using workflow path identity, migrating entries when a workflow is
renamed instead of deleting them by the old name. Preserve intervals restored by
carry_forward_triggers when definition lookup fails, while still removing
intervals after a successful lookup confirms the workflow no longer has a cron
schedule.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b3143f2-8838-4e58-bc94-3d3a426008ab

📥 Commits

Reviewing files that changed from the base of the PR and between 800bc51 and 7e959e2.

📒 Files selected for processing (2)
  • github-ci-exporter/src/collector.rs
  • github-ci-exporter/src/github/rest.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • github-ci-exporter/src/github/rest.rs

Comment thread github-ci-exporter/src/collector.rs Outdated
The interval cache was keyed by display name while workflow identity is
the file path -- the same inconsistency already fixed in the run reducer.
Renaming a workflow whose contents lookup then failed orphaned its
cached interval: carry_forward_triggers restored the triggers by path,
but the retain step pruned the interval because the old display name was
no longer in the live set. workflow_expected_interval_seconds vanished
for that cycle, flapping GitHubScheduledWorkflowStale.

Intervals are now keyed by path throughout, and record_runs maps the
run's display name to its path through the cached workflow set, so the
exported metric keeps its human-readable workflow label.

Verified by reverting to name-keying: 4 of the interval tests fail.
Confirmed against the live API that the metric still populates -- 36
series, unchanged in shape.
@fredclausen
fredclausen enabled auto-merge August 10, 2026 18:10
@fredclausen
fredclausen merged commit 9c2fa65 into main Aug 10, 2026
8 checks passed
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.

2 participants