Skip to content

fix: set initial Pending status condition on queued PipelineRuns - #511

Merged
gbenhaim merged 1 commit into
tektoncd:mainfrom
ankrsinha:fix-pending-status-condition
Aug 5, 2026
Merged

fix: set initial Pending status condition on queued PipelineRuns#511
gbenhaim merged 1 commit into
tektoncd:mainfrom
ankrsinha:fix-pending-status-condition

Conversation

@ankrsinha

Copy link
Copy Markdown
Contributor

What

Sets an initial Succeeded=Unknown / Reason=PipelineRunPending status condition on PipelineRuns that are queued by tekton-kueue.

A lightweight status reconciler is registered alongside the existing Kueue workload reconciler in SetupWithManager. When it sees a PipelineRun with spec.status=PipelineRunPending and empty status.conditions, it writes the pending condition via the status subresource.

Why

The tekton-kueue webhook sets spec.managedBy=kueue.x-k8s.io/multikueue on every PipelineRun. Tekton's own controller filters out any PipelineRun whose managedBy is not tekton.dev/pipeline, so it never reconciles these runs and never populates status.conditions.

Since kubectl get pipelinerun, tkn/opc, and the OpenShift Console UI all read status.conditions to display status, queued PipelineRuns show a blank/--- status instead of Pending - making it look like the run is broken rather than waiting for admission.

Changes

  • internal/controller/pipelinerun_controller.go: Added pipelineRunStatusReconciler and registered it in SetupWithManager. Added RBAC marker for pipelineruns/status.
  • internal/controller/pipelinerun_controller_test.go: Tests covering all reconciler paths (not found, not pending, already has condition, sets condition, update failure).
  • config/rbac/role.yaml: Auto-generated - adds patch/update on pipelineruns/status.

Verification

  • make lint passes
  • make test passes
  • Tested with tekton-kueue mutate CLI (if CEL/webhook changes)
  • make manifests and make generate run cleanly (if API/RBAC changes)

@qodo-app-for-konflux-ci

Copy link
Copy Markdown

PR Summary by Qodo

Set initial Pending status condition for queued Tekton PipelineRuns

🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a lightweight controller to set an initial Pending condition on queued PipelineRuns.
• Update RBAC to allow updating the PipelineRun status subresource.
• Add unit tests covering the status reconciler decision paths and failure handling.
Diagram

graph TD
  M["controller-runtime Manager"] --> S["Status reconciler"] --> API["Kubernetes API"] --> PRS["pipelineruns/status"]
  M --> K["Kueue reconciler"] --> API
  PR["PipelineRun"] -->|"watch"| S
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Set condition in the mutating webhook
  • ➕ Single write at creation time; no extra controller to run/watch.
  • ➕ Immediate UI/CLI feedback without waiting for a reconcile loop.
  • ➖ Webhook would need status permissions (often avoided) and must use status subresource correctly.
  • ➖ Harder to handle retries/conflicts; webhooks should stay lightweight and failure-averse.
  • ➖ Doesn’t cover cases where objects are created/modified without passing the webhook (e.g., backups/restore, some controllers).
2. Only set condition when creating/admitting the Kueue Workload
  • ➕ Ties the status update to the exact moment the object becomes queued/admitted, avoiding a generic watch.
  • ➕ May reduce reconcile noise if you can update status alongside existing lifecycle transitions.
  • ➖ More coupling to Kueue jobframework flow and more complex to ensure it runs for all relevant paths.
  • ➖ Harder to guarantee the condition exists early enough for good UX (right after creation).
3. Change managedBy usage so Tekton controller can reconcile
  • ➕ Lets Tekton’s native controller populate full status, not just an initial Pending condition.
  • ➕ Potentially removes the need for a supplemental status controller.
  • ➖ May be incompatible with multi-kueue architecture/ownership expectations.
  • ➖ Higher-risk behavioral change across clusters/controllers; likely broader design discussion required.

Recommendation: Keep the PR’s lightweight dedicated status reconciler. It cleanly addresses the user-visible regression (blank status) with minimal coupling to Kueue internals, uses the Kubernetes status subresource as intended, and is covered by targeted tests. The webhook-based approach is tempting for immediacy but adds operational risk and heavier responsibilities to the admission path.

Files changed (3) +201 / -1

Bug fix (1) +47 / -1
pipelinerun_controller.goAdd and register PipelineRun pending-status reconciler +47/-1

Add and register PipelineRun pending-status reconciler

• Registers an additional controller-runtime reconciler alongside the existing Kueue reconciler. The new reconciler watches PipelineRuns and, when spec.status is Pending and status.conditions is empty, writes a Succeeded=Unknown condition with reason PipelineRunPending via the status subresource (requires new RBAC marker).

internal/controller/pipelinerun_controller.go

Tests (1) +147 / -0
pipelinerun_controller_test.goAdd unit tests for pipelineRunStatusReconciler +147/-0

Add unit tests for pipelineRunStatusReconciler

• Introduces Ginkgo tests validating the status reconciler behavior: not-found handling, skipping non-pending runs, skipping runs with existing Succeeded condition, setting the pending condition, and surfacing status-update failures.

internal/controller/pipelinerun_controller_test.go

Other (1) +7 / -0
role.yamlGrant patch/update permissions on pipelineruns/status +7/-0

Grant patch/update permissions on pipelineruns/status

• Adds RBAC rules allowing patch and update on the Tekton PipelineRun status subresource so the new reconciler can write initial conditions.

config/rbac/role.yaml

@qodo-app-for-konflux-ci

qodo-app-for-konflux-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 10 rules

Grey Divider


Action required

1. Missing managedBy guard ✓ Resolved 🐞 Bug ≡ Correctness
Description
pipelineRunStatusReconciler.Reconcile writes a Pending Succeeded condition for any PipelineRun with
spec.status=Pending and no Succeeded condition, but it never checks spec.managedBy even though the
controller is described as only needed when managedBy is set. This can incorrectly mutate
Tekton-managed PipelineRuns that are briefly Pending and create status-update conflicts/churn.
Code

internal/controller/pipelinerun_controller.go[R315-331]

+	if plr.Spec.Status != tekv1.PipelineRunSpecStatusPending {
+		return ctrl.Result{}, nil
+	}
+
+	if plr.Status.GetCondition(kapi.ConditionSucceeded) != nil {
+		return ctrl.Result{}, nil
+	}
+
+	plr.Status.Conditions = append(plr.Status.Conditions, kapi.Condition{
+		Type:               kapi.ConditionSucceeded,
+		Status:             corev1.ConditionUnknown,
+		Reason:             "PipelineRunPending",
+		Message:            "PipelineRun is pending, waiting for Kueue admission",
+		LastTransitionTime: kapi.VolatileTime{Inner: metav1.Now()},
+	})
+
+	return ctrl.Result{}, r.client.Status().Update(ctx, &plr)
Relevance

⭐⭐ Medium

Correctness guard seems reasonable, but no close repo precedent specifically about managedBy scoping
in reconcilers.

PR-#279
PR-#76

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code comments and docs describe this reconciler as a workaround for the managedBy/MultiKueue
case, but the implementation only checks spec.status and Succeeded condition presence, so it
will act on unrelated PipelineRuns as well. The webhook sets spec.status=Pending for all
PipelineRuns on create, making the overly-broad predicate especially likely to trigger.

internal/controller/pipelinerun_controller.go[114-118]
internal/controller/pipelinerun_controller.go[309-321]
internal/webhook/v1/pipelinerun_webhook.go[155-166]
README.md[120-126]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`pipelineRunStatusReconciler` is intended to set an initial Pending condition only for PipelineRuns that Tekton won’t reconcile when `spec.managedBy` is set (MultiKueue override mode). However, it currently applies to **any** PipelineRun with `spec.status=Pending` and no `Succeeded` condition, which can affect normal Tekton-managed PipelineRuns and create unnecessary reconcile/update churn.

### Issue Context
- The controller registration comment explicitly frames this controller as needed when `spec.managedBy` is set.
- The webhook always sets `spec.status=Pending` on create, and only sets `spec.managedBy` when MultiKueue override is enabled.

### Fix Focus Areas
- internal/controller/pipelinerun_controller.go[309-332]
- internal/controller/pipelinerun_controller_test.go[513-655]

### What to change
1. In `Reconcile`, add an early guard that returns unless the PipelineRun is the intended target (e.g., `plr.Spec.ManagedBy != nil && *plr.Spec.ManagedBy == common.ManagedByMultiKueueLabel`).
2. (Optional but recommended) Also match the stated behavior by requiring empty conditions (`len(plr.Status.Conditions)==0`) if that’s the real intent.
3. Add unit tests:
  - Pending PipelineRun **without** `spec.managedBy` does **not** get a condition.
  - Pending PipelineRun **with** `spec.managedBy=common.ManagedByMultiKueueLabel` gets the pending condition.
4. Consider adding an event filter/predicate in `SetupWithManager` to reduce reconciles (only enqueue when `spec.status==Pending` and `spec.managedBy` matches and `Succeeded` condition is absent).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread internal/controller/pipelinerun_controller.go Outdated
@ankrsinha
ankrsinha force-pushed the fix-pending-status-condition branch from 54bfa48 to 92b0333 Compare July 24, 2026 08:24
@ankrsinha

Copy link
Copy Markdown
Contributor Author

/kind bug

// controller skips PipelineRuns with spec.managedBy set, so without this
// reconciler the status stays blank in CLI and UI tools until Kueue admits
// the workload and dispatches it to a spoke cluster.
type pipelineRunStatusReconciler struct {

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.

Please keep this reconciler in its own file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Made the changes, moved the reconciler into its own new file.

Type: kapi.ConditionSucceeded,
Status: corev1.ConditionUnknown,
Reason: "PipelineRunPending",
Message: "PipelineRun is pending, waiting for Kueue admission",

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.

Lets say waiting for multikueue admission

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the Message field accordingly.

@ankrsinha

Copy link
Copy Markdown
Contributor Author

@pramodbindal I have made the changes as per your suggestions, please have a look.

client client.Client
}

func (r *pipelineRunStatusReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we add this logic to the existing controller https://github.com/konflux-ci/tekton-kueue/blob/main/internal/controller/pipelinerun_controller.go ?

There are many hook function that kueue's job framework provides, can we use any of those ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@gbenhaim I checked the available hook interfaces in kueue's job framework. The hooks that have client access (such as Stop, Finalize, and GetCustomAnnotations) are only invoked during eviction, finalization, or workload slicing.

They aren't called while a PipelineRun is in pending / yet to be admitted state, which is when we need to update the status condition. So I don't think the existing hooks can handle this use case.

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.

@ankrsinha please add the unit tests

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.

@gbenhaim
Here we are updating the pipeline which suspended and will be picked by one of the spoke clusters.

I think adding a dedicated reconciler is right thing to do

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified by adding debug logs to existing hooks, that the hooks that get called are PodSets, Finished, IsSuspended and isActive, but these are pure data methods with no client access, so cannot make API calls.
Screenshot From 2026-07-30 15-24-28

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@pramodbindal Added the unit tests.

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.

@gbenhaim please review again

@ankrsinha
ankrsinha force-pushed the fix-pending-status-condition branch from c6e9feb to 574d25c Compare July 30, 2026 11:20

@gbenhaim gbenhaim left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This review was posted by Gal's Cursor.

Comment thread internal/controller/pipelinerun_status_reconciler.go
return err
}

return SetupPendingStatusWithManager(mgr)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: Following kubebuilder conventions, each controller should be registered independently from the entrypoint (cmd/main.go), rather than having one controller register another. This makes the lifecycle explicit and consistent with how SetupIndexer and the ConfigMapReconciler are already registered separately.

Move SetupPendingStatusWithManager(mgr) to cmd/main.go:

// cmd/main.go
err = controller.SetupWithManager(ctx, mgr)
// ...
if err = controller.SetupPendingStatusWithManager(mgr); err != nil {
	setupLog.Error(err, "Failed to setup pending status controller")
	os.Exit(1)
}

And revert SetupWithManager to return directly:

return reconciler.SetupWithManager(mgr)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved the controller registration to cmd/main.go, SetupWithManager now returns directly (reverted all the changes in pipelinerun_controller.go).

return ctrl.Result{}, nil
}

plr.Status.Conditions = append(plr.Status.Conditions, kapi.Condition{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: PipelineRunStatus has a SetCondition method that handles deduplication and keeps conditions sorted. While the GetCondition != nil guard above prevents duplicates, using SetCondition is more defensive and idiomatic with the Tekton API:

plr.Status.SetCondition(&kapi.Condition{
	Type:               kapi.ConditionSucceeded,
	Status:             corev1.ConditionUnknown,
	Reason:             "PipelineRunPending",
	Message:            "PipelineRun is pending, waiting for MultiKueue admission",
	LastTransitionTime: kapi.VolatileTime{Inner: metav1.Now()},
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Switched to SetCondition as suggested.

Comment thread internal/controller/pipelinerun_status_reconciler.go
@ankrsinha
ankrsinha force-pushed the fix-pending-status-condition branch from 574d25c to f2e5753 Compare August 3, 2026 11:27
@ankrsinha

Copy link
Copy Markdown
Contributor Author

@gbenhaim I have updated the PR with all the changes suggested in the review comments.

@tekton-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: gbenhaim, pramodbindal

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@pramodbindal

Copy link
Copy Markdown
Member

/retest

@pramodbindal pramodbindal reopened this Aug 4, 2026
Signed-off-by: Ankur Sinha <anksinha@redhat.com>
@ankrsinha
ankrsinha force-pushed the fix-pending-status-condition branch 2 times, most recently from f2e5753 to fa2102c Compare August 4, 2026 08:15
@codecov-commenter

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@gbenhaim
gbenhaim merged commit 539fe07 into tektoncd:main Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants