Skip to content

fix(reconciler): stop reporting pipelineRun status when check run id patch fails - #2930

Open
pujitha24 wants to merge 2 commits into
tektoncd:mainfrom
pujitha24:auto/issue-1828
Open

fix(reconciler): stop reporting pipelineRun status when check run id patch fails#2930
pujitha24 wants to merge 2 commits into
tektoncd:mainfrom
pujitha24:auto/issue-1828

Conversation

@pujitha24

@pujitha24 pujitha24 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

📝 Description of the Change

ReconcileKind's check-run-ID 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.

This extracts the gate into waitingForCheckRunID and scopes it to only apply while the PipelineRun is still running: !pr.IsDone() && !pr.IsCancelled(). Once it is done or cancelled, reconciliation proceeds 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.

🔗 Linked GitHub Issue

Fixes #1828

🧪 Testing Strategy

  • Unit tests
  • Integration tests
  • End-to-end tests
  • Manual testing
  • Not Applicable

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.

🤖 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.

  • I have not used any AI assistance for this PR.
  • I have used AI assistance for this PR.

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-by trailer to your commit message.
For example:

Co-authored-by: Claude noreply@anthropic.com

✅ Submitter Checklist

  • 📝 My commit messages are clear, informative, and follow the project's How to write a git commit message guide. The Gitlint linter ensures in CI it's properly validated
  • ✨ I have ensured my commit message prefix (e.g., fix:, feat:) matches the "Type of Change" I selected above.
  • ♽ I have run make test and make lint locally to check for and fix any
    issues. For an efficient workflow, I have considered installing
    pre-commit and running pre-commit install to
    automate these checks.
  • 📖 I have added or updated documentation for any user-facing changes.
  • 🧪 I have added sufficient unit tests for my code changes.
  • 🎁 I have added end-to-end tests where feasible. See README for more details.
  • 🔎 I have addressed any CI test flakiness or provided a clear reason to bypass it.
  • If adding a provider feature, I have filled in the following and updated the provider documentation:
    • GitHub App
    • GitHub Webhook
    • Gitea/Forgejo
    • GitLab
    • Bitbucket Cloud
    • Bitbucket Data Center

AI assistance: this change was drafted with Claude Code.

…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>
Copilot AI lite review requested due to automatic review settings August 18, 2026 19:41

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 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 into waitingForCheckRunID and limited it to still-running PipelineRuns.
  • Updated ReconcileKind to 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.

Comment thread pkg/reconciler/reconciler.go Outdated
Comment on lines +121 to +123
// 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)
Comment on lines +154 to +158
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)
@pujitha24

Copy link
Copy Markdown
Contributor Author

Thanks — went through the automated review. Applied two of the three, and I want to be explicit about the one I didn't.

Applied:

  • Reworded the waitingForCheckRunID doc comment — CheckRunID used consistently, and the vague "it lost a resource conflict" replaced with the actual failure mode (the patch losing a resource-version conflict under high parallelism).
  • Added the missing IsCancelled() case to TestWaitingForCheckRunID. The point about IsCancelled() reading Spec.Status rather than the Succeeded condition was correct and worth catching — the table needed a specStatus field and the PipelineRun under test needed a Spec, otherwise the branch was never exercised at all. I checked the new case actually has teeth by removing the !pr.IsCancelled() guard: only that case fails, and it passes again once restored.

Not applied: reversing the assert.Equal arguments. This file is mixed on ordering, and the adjacent table-driven tests (reconciler_test.go:561-562) use assert.Equal(t, actual, expected) in exactly the same shape as this one, so switching it would make the file less locally consistent rather than more. Happy to change it if you'd rather standardise the whole file in one go.

Locally: gofmt clean, go build ./..., go vet ./pkg/reconciler/..., and go test ./pkg/reconciler/... all pass. Noting that because the e2e legs on this PR never actually run — they stop at Check user permissions on PRs, so there's no CI signal here either way.

Comment on lines -200 to +221
// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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 ??

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.

Incorrect PR Status for Tekton Pipeline Runs with Multiple Failed Tasks

3 participants