fix(reconciler): stop reporting pipelineRun status when check run id patch fails - #2930
fix(reconciler): stop reporting pipelineRun status when check run id patch fails#2930pujitha24 wants to merge 2 commits into
Conversation
…patch fails Motivation: GitHub App PipelineRuns can get permanently stuck showing "In progress" on GitHub even after they finish. Report: tektoncd#1828 Approach: ReconcileKind's checkRunID gate returned nil unconditionally for any GitHub App pipelineRun missing the CheckRunID annotation, with no way to ever unblock it. Since createCheckRunStatus creates the GitHub check run before action.PatchPipelineRun writes the CheckRunID annotation back onto the object, a resource-version conflict on that patch (more likely with many parallel tasks updating the pipelineRun) leaves the check run created on GitHub as "in_progress" but the annotation never lands. updatePipelineRunToInProgress swallows that error and already marks SCMReportingPLRStarted=true, so the reconciler never retries reporting "in progress" again, and once the pipelineRun finishes the gate blocks it forever: nothing ever reports the final status. Extracted the gate into waitingForCheckRunID and scoped it to only apply while the pipelineRun is still running: !pr.IsDone() && !pr.IsCancelled(). Once it is done or cancelled we proceed even without the annotation. getOrUpdateCheckRunStatus in pkg/provider/github/status.go already falls back to looking up the existing check run by external ID (getExistingCheckRunID) or creating a new one (createCheckRunStatus) when the annotation is missing, so this lets a finished pipelineRun's status land instead of leaving the check run stuck at "in progress" forever. User-visible behavior is unchanged for the common case where the patch succeeds. The fix only changes behavior for the specific failure path described above, where the final status previously never reported at all. Validation: - go build ./... - go test ./pkg/reconciler/... and go test ./pkg/provider/github/... (packages touched by this change and by the affected code path) - make test (full repo test suite) passes - Added TestWaitingForCheckRunID, a table-driven unit test covering: non-GitHub-App pipelineRuns, pipelineRuns with the annotation already set, a still-running pipelineRun missing the annotation (should keep waiting), and a finished pipelineRun missing the annotation (should no longer be gated). This was not validated against a live GitHub App installation reproducing the original resource-version conflict; the fix and its downstream fallback path (existing check-run lookup and creation) are covered by unit tests only. ```release-note Fix pipelineRun status getting permanently stuck as "in progress" on GitHub when the check run id fails to be patched onto the pipelineRun, by allowing a finished pipelineRun to still report its final status. ``` Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a GitHub App reconciliation edge case where a PipelineRun could get permanently blocked from reporting its final GitHub check status if patching the CheckRunID annotation fails (e.g., resource-version conflicts under high parallelism). The change scopes the “wait for CheckRunID” gate to only apply while the PipelineRun is still running, allowing completed runs to proceed and rely on the provider’s existing fallback logic (lookup/create check run without the annotation).
Changes:
- Extracted the “missing
CheckRunID” gate intowaitingForCheckRunIDand limited it to still-runningPipelineRuns. - Updated
ReconcileKindto use the new gate function. - Added a table-driven unit test for
waitingForCheckRunID.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pkg/reconciler/reconciler.go | Adds waitingForCheckRunID and uses it to avoid blocking final status reporting when the annotation never lands. |
| pkg/reconciler/reconciler_test.go | Adds unit coverage for the new gating behavior. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // cancelled we stop waiting even if the check run id is still missing, so a | ||
| // pipelineRun whose check run id patch never landed (e.g. it lost a resource | ||
| // conflict) is not stuck reporting "in progress" forever: the provider can |
| }, | ||
| }, | ||
| } | ||
| assert.Equal(t, waitingForCheckRunID(pr), tt.want) |
| name: "GitHub App pipelineRun done without check run id", | ||
| annotations: map[string]string{keys.InstallationID: "1234"}, | ||
| conditions: doneCondition, | ||
| want: false, | ||
| }, |
Two changes from the automated review on tektoncd#2930: - Reword the waitingForCheckRunID doc comment: use CheckRunID consistently rather than alternating with "check run id", and replace the unclear "it lost a resource conflict" with the concrete failure mode (the patch losing a resource-version conflict under high parallelism). - Cover the IsCancelled() branch in TestWaitingForCheckRunID. PipelineRun's IsCancelled() reads Spec.Status, not the Succeeded condition, so the table gains a specStatus field and the pipelineRun under test now sets Spec. Without that the branch was never exercised. Verified by removing the !pr.IsCancelled() guard: only the new case fails, and it passes again once restored. The third suggestion, reversing the assert.Equal arguments, is not applied. This file is mixed, and the adjacent table-driven tests at reconciler_test.go:561-562 use assert.Equal(t, actual, expected) exactly as this one does, so the change would make it less locally consistent, not more. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com> Assisted-by: claude-sonnet-5 (via Claude Code)
|
Thanks — went through the automated review. Applied two of the three, and I want to be explicit about the one I didn't. Applied:
Not applied: reversing the Locally: |
| // if its a GitHub App pipelineRun PR then process only if check run id is added otherwise wait | ||
| if _, ok := pr.Annotations[keys.InstallationID]; ok { | ||
| if _, ok := pr.Annotations[keys.CheckRunID]; !ok { | ||
| return nil | ||
| } | ||
| if waitingForCheckRunID(pr) { | ||
| return nil |
There was a problem hiding this comment.
I think we should remove any gate here instead because getOrUpdateCheckRunStatus already checks for existing check-run id in checks on PR or via check-run id annotation so no need to check it, wdyt @chmouel ??
📝 Description of the Change
ReconcileKind's check-run-ID gate returnednilunconditionally for any GitHub AppPipelineRunmissing theCheckRunIDannotation, with no way to ever unblock it. SincecreateCheckRunStatuscreates the GitHub check run beforeaction.PatchPipelineRunwrites theCheckRunIDannotation back onto the object, a resource-version conflict on that patch (more likely with many parallel tasks updating thePipelineRun) leaves the check run created on GitHub asin_progressbut the annotation never lands.updatePipelineRunToInProgressswallows that error and already marksSCMReportingPLRStarted=true, so the reconciler never retries reporting "in progress" again, and once thePipelineRunfinishes the gate blocks it forever — nothing ever reports the final status.This extracts the gate into
waitingForCheckRunIDand scopes it to only apply while thePipelineRunis still running:!pr.IsDone() && !pr.IsCancelled(). Once it is done or cancelled, reconciliation proceeds even without the annotation.getOrUpdateCheckRunStatusinpkg/provider/github/status.goalready falls back to looking up the existing check run by external ID (getExistingCheckRunID) or creating a new one (createCheckRunStatus) when the annotation is missing, so this lets a finishedPipelineRun's status land instead of leaving the check run stuck at "in progress" forever.User-visible behavior is unchanged for the common case where the patch succeeds. The fix only changes behavior for the specific failure path described above, where the final status previously never reported at all.
🔗 Linked GitHub Issue
Fixes #1828
🧪 Testing Strategy
Added
TestWaitingForCheckRunID, a table-driven unit test covering: non-GitHub-AppPipelineRuns,PipelineRuns with the annotation already set, a still-runningPipelineRunmissing the annotation (should keep waiting), and a finishedPipelineRunmissing the annotation (should no longer be gated). This was not validated against a live GitHub App installation reproducing the original resource-version conflict; the fix and its downstream fallback path (existing check-run lookup and creation) are covered by unit tests only.🤖 AI Assistance
AI assistance can be used for various tasks, such as code generation,
documentation, or testing.
Please indicate whether you have used AI assistance
for this PR and provide details if applicable.
Important
Slop will be simply rejected, if you are using AI assistance you need to make sure you
understand the code generated and that it meets the project's standards. you
need at least know how to run the code and deploy it (if needed). See
startpaac to make it easy
to deploy and test your code changes.
If the majority of the code in this PR was generated by an AI, please add a
Co-authored-bytrailer to your commit message.For example:
Co-authored-by: Claude noreply@anthropic.com
✅ Submitter Checklist
fix:,feat:) matches the "Type of Change" I selected above.make testandmake lintlocally to check for and fix anyissues. For an efficient workflow, I have considered installing
pre-commit and running
pre-commit installtoautomate these checks.
AI assistance: this change was drafted with Claude Code.