From 32d4c2ff890ce41f9eae1795e137df11980f3270 Mon Sep 17 00:00:00 2001 From: ab-ghosh Date: Wed, 6 May 2026 22:20:37 +0530 Subject: [PATCH] fix: retry VCS status on finalize for failed state When postFinalStatus fails during reconciliation, the PipelineRun state is set to "failed" but FinalizeKind never retries the VCS update. This leaves commit statuses stuck at "pending" with no recovery path. Add a StateFailed handler in FinalizeKind that retries the status post one last time during PipelineRun cleanup. Signed-off-by: ab-ghosh Assisted-by: Claude Opus 4.6 (via Claude Code) --- pkg/reconciler/finalizer.go | 41 ++++++++++++++++++++++++++++++++ pkg/reconciler/finalizer_test.go | 26 ++++++++++++++++---- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/pkg/reconciler/finalizer.go b/pkg/reconciler/finalizer.go index aa019de07f..e5371baff3 100644 --- a/pkg/reconciler/finalizer.go +++ b/pkg/reconciler/finalizer.go @@ -69,6 +69,47 @@ func (r *Reconciler) FinalizeKind(ctx context.Context, pr *tektonv1.PipelineRun) return nil } } + + if state == kubeinteraction.StateFailed { + repoName, ok := pr.GetAnnotations()[keys.Repository] + if !ok { + return nil + } + repo, err := r.repoLister.Repositories(pr.Namespace).Get(repoName) + if errors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + if err := r.retryFinalStatusUpdate(ctx, repo, pr); err != nil { + logger.Errorf("failed to retry final status update on finalize: %v", err) + } + } + + return nil +} + +func (r *Reconciler) retryFinalStatusUpdate(ctx context.Context, repo *v1alpha1.Repository, pr *tektonv1.PipelineRun) error { + logger := logging.FromContext(ctx) + pacInfo := r.run.Info.GetPacOpts() + + detectedProvider, event, err := r.initGitProviderClient(ctx, logger, repo, pr) + if err != nil { + return err + } + + if r.run.Clients.Log == nil { + r.run.Clients.Log = logger + } + + _, _, err = r.postFinalStatus(ctx, logger, &pacInfo, detectedProvider, event, pr) + if err != nil { + return fmt.Errorf("failed to post final status on finalize: %w", err) + } + + logger.Infof("successfully retried final status update for pipelineRun %s/%s", pr.GetNamespace(), pr.GetName()) return nil } diff --git a/pkg/reconciler/finalizer_test.go b/pkg/reconciler/finalizer_test.go index 18551061b5..113fe5438c 100644 --- a/pkg/reconciler/finalizer_test.go +++ b/pkg/reconciler/finalizer_test.go @@ -8,6 +8,7 @@ import ( "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys" "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" "github.com/openshift-pipelines/pipelines-as-code/pkg/consoleui" + "github.com/openshift-pipelines/pipelines-as-code/pkg/events" "github.com/openshift-pipelines/pipelines-as-code/pkg/kubeinteraction" "github.com/openshift-pipelines/pipelines-as-code/pkg/params" "github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients" @@ -20,7 +21,9 @@ import ( "go.uber.org/zap" zapobserver "go.uber.org/zap/zaptest/observer" "gotest.tools/v3/assert" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" "knative.dev/pkg/logging" rtesting "knative.dev/pkg/reconciler/testing" ) @@ -126,6 +129,18 @@ func TestReconcilerFinalizeKind(t *testing.T) { getTestPR("pr3", kubeinteraction.StateQueued), }, }, + { + name: "failed state retries final status", + pipelinerun: func() *tektonv1.PipelineRun { + pr := getTestPR("pr1", kubeinteraction.StateFailed) + pr.Status.Conditions = append(pr.Status.Conditions, apis.Condition{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionFalse, + Reason: tektonv1.PipelineRunReasonFailed.String(), + }) + return pr + }(), + }, } for _, tt := range tests { @@ -134,6 +149,7 @@ func TestReconcilerFinalizeKind(t *testing.T) { ctx = logging.WithLogger(ctx, fakelogger) testData := testclient.Data{ Repositories: []*v1alpha1.Repository{finalizeTestRepo}, + PipelineRuns: []*tektonv1.PipelineRun{tt.pipelinerun}, } if tt.skipAddingRepo { testData.Repositories = []*v1alpha1.Repository{} @@ -148,6 +164,7 @@ func TestReconcilerFinalizeKind(t *testing.T) { cs := ¶ms.Run{ Clients: clients.Clients{ PipelineAsCode: stdata.PipelineAsCode, + Tekton: stdata.Pipeline, Log: fakelogger, }, Info: info.Info{ @@ -158,10 +175,11 @@ func TestReconcilerFinalizeKind(t *testing.T) { } cs.Clients.SetConsoleUI(consoleui.FallBackConsole{}) r := Reconciler{ - repoLister: informers.Repository.Lister(), - qm: queuepkg.NewManager(fakelogger), - run: cs, - kinteract: kinterfaceTest, + repoLister: informers.Repository.Lister(), + qm: queuepkg.NewManager(fakelogger), + run: cs, + kinteract: kinterfaceTest, + eventEmitter: events.NewEventEmitter(stdata.Kube, fakelogger), } if len(tt.addToQueue) != 0 {