Respect required CI checks#16
Conversation
Show approval-gated non-blocking checks separately while keeping merge status based on required GitHub checks.
Use statusCheckRollup.state when required metadata and individual checks are unavailable, including EXPECTED as pending. Expand required-CI and parameterized test coverage around the fallback paths.
Treat not-started required CI as needing attention and avoid counting waiting approval parent checks as active CI work.
Replace name-based approval gate detection (checking for "approve"/"approval" in check names) with GitHub's authoritative signal: CheckRun status WAITING, which is set exclusively when a job is blocked on environment protection approval. - Remove isApprovalGate(), approvalGateName(), parentWorkflowNameForApprovalGate() - approvalParentWorkflowNames() now identifies parents via WAITING CheckRuns - hasActiveNonApprovalWork() guards on status != WAITING instead of name check - StatusContext PENDING/EXPECTED always maps to .pending (no name override) - isRequiredAggregateWork excludes WAITING checks, not name-matched ones - Update fixtures to use WAITING CheckRuns in place of approval-named StatusContexts
- Sort requiredStatusCheckContexts after Set deduplication so ordering is stable across calls - Simplify non-blocking summary ForEach to use Identifiable directly, replacing enumerated index with last-element id comparison
lukelabonte
left a comment
There was a problem hiding this comment.
I left two comments on the WAITING handling. I think using WAITING is the right direction, but I’m not sure we want to make it the only approval-gate signal or treat every WAITING check as approval-related.
| } | ||
|
|
||
| private func parseStatusChecks(from checks: [GHPRDetailResponse.StatusCheck]?) -> [StatusCheck] { | ||
| private func hasActiveNonApprovalWork( |
There was a problem hiding this comment.
I think this now treats every WAITING check as approval-related work.
That might be right for GitHub Actions environment approvals, but the code doesn’t check that the waiting check is actually an approval gate. If GitHub or another provider returns a non-approval check in WAITING, we could show the PR as Not started even though CI is active.
Could we either scope this more tightly, or add a test that proves a non-approval WAITING check doesn’t get hidden?
| } | ||
|
|
||
| let hasRequiredMetadata = requiredContextSet != nil || checks.contains { $0.isRequired != nil } | ||
| return hasRequiredMetadata ? [] : checks |
There was a problem hiding this comment.
I think this drops the fallback for approval gates that come through as legacy StatusContext entries.
The old path handled contexts like ci/circleci: dead_code_cleanup/approve_dead_code_cleanup by treating them as waiting for approval. This now only works if we get a CheckRun with status: WAITING.
Are we sure external providers won’t still send approval gates as StatusContext + PENDING? If not, I think we should keep the WAITING path but leave the old name-based fallback for legacy contexts.
Summary
This PR is based on #10 by @lukelabonte (Luke LaBonte), rebased cleanly onto main and extended with a few additional fixes. All four of Luke's commits are preserved with his authorship intact.
What Luke's work does
isRequired,branchProtectionRule).notStartedbuild status (grayplay.slashicon) for PRs where required CI hasn't kicked off yet — previously these showed a misleading green checkmark.GHPRViewResponsestruct.What was added on top
WAITINGstatus. The original used name heuristics ("approve"/"approval" in check names) to identify approval gates. In GitHub Actions,CheckRun.status == WAITINGis the authoritative signal — set exclusively when a job is blocked on environment protection approval. The heuristic functions (isApprovalGate,approvalGateName,parentWorkflowNameForApprovalGate) are removed;WAITINGstatus drives all approval gate logic. Test fixtures updated to useWAITINGCheckRuns instead of approval-named StatusContexts.Array(Set(...))now has.sorted()to give stable ordering across calls.ForEachin non-blocking summary. ReplacedForEach(Array(summary.segments.enumerated()), id: \.element.id)withForEach(summary.segments)usingIdentifiabledirectly.Test plan
play.slash, not green checkmarkxcodebuild -project MonitorLizard/MonitorLizard.xcodeproj -scheme MonitorLizard -destination 'platform=macOS' -configuration Debug CODE_SIGNING_ALLOWED=NO test