#7518·crossplane

Proposal: Surface composition function pipeline step results in XR status for self-service debugging

Author: bhavyamsharmaaCreated Jun 16, 2026Updated Sep 15, 2026
Labelsstale

What problem are you facing?

When a composition function pipeline runs and something goes wrong, Crossplane surfaces the outcome as a Synced=False condition with a message. For SEVERITY_FATAL results, the step name and message are included:

Synced: False
Message: cannot run Composition pipeline step "patch-and-transform":
         pipeline step "patch-and-transform" returned a fatal result:
         field 'spec.id' is required but not set

For SEVERITY_WARNING results, Crossplane emits Kubernetes events on the XR. Both of these are useful, but they leave a significant debugging gap in practice:

  1. Events are ephemeral. The Kubernetes API server prunes events after approximately one hour by default, and bounds the total count per object. For a large XR that has many composed resources emitting warnings, older warning events are evicted before they can be investigated. A user who runs kubectl describe an hour after an incident sees no record of what happened.

  2. The Synced condition only records the terminal state. A pipeline with three steps where step 2 fails tells you step 2 failed. It tells you nothing about what step 1 returned, or what composed resources step 2 had already desired before failing.

  3. The Prometheus metrics aggregate across XRs. The existing function_run_function_seconds / function_run_function_response_total metrics in internal/xfn/function_runner_metrics.go are labeled by function_name, function_package, and result_severity, but not by pipeline step name or the XR that triggered the call. If the same function (e.g. function-go-templating) is used in three pipeline steps and one step is consistently slow or failing, there is no way to tell which step from metrics alone.

  4. Debugging requires cluster log access. To understand what a pipeline actually did - which steps ran, what each function returned, which composed resources were desired - a platform engineer must have access to Crossplane pod logs and must correlate log entries by XR name and timestamp. Many organisations do not grant developers that access.

Crossplane's own observability guide explicitly states:

Scenario 1 [a person using a system can't figure out why things aren't working] is best served by events in the context of Crossplane, since the users may not have access to read logs or metrics, and even if they did, it would be hard to relate them back to the event the user is trying to understand.

The current state breaks this contract for any composition pipeline that produces warnings rather than a fatal error.

How could Crossplane help solve your problem?

Add a status.pipeline struct to Composite Resource status that records a durable summary of the last composition function pipeline execution. Unlike events (ephemeral) and Prometheus metrics (aggregated, no per-XR resolution), this field persists across reconciles and is directly readable by anyone who can kubectl get the XR.

A minimal proposal:

yaml
status:
  pipeline:
    lastRunAt: "2026-06-15T10:34:12Z"
    durationMs: 342
    steps:
      - name: "render-namespace"
        functionRef: "function-go-templating"
        severity: Normal
      - name: "patch-and-transform"
        functionRef: "function-patch-and-transform"
        severity: Warning
        message: "composed resource \"my-bucket\" is not yet ready"
      - name: "set-status"
        functionRef: "function-go-templating"
        severity: Normal

Key design points:

  • Durable: written as a standard XR status field via server-side apply, not as an event. Survives restarts and time.
  • Low cardinality: step entries record severity and at most one message per step (the first warning or fatal result). No unbounded lists.
  • No secrets leakage: function responses may include arbitrary data in context, but only the Results slice (severity + message) is summarised here. The full desired state is not stored.
  • Not a replacement for Healthy (#5643): the proposed Healthy condition aggregates composed resource readiness (infrastructure-level). This is about the pipeline execution itself (composition function-level). Both are needed.

Implementation sketch

The per-step results are already available today:

  • Each RunFunctionResponse contains a Results slice (fnv1.Result with Severity and Message).
  • The existing FunctionPipeline.Run path in internal/controller/apiextensions/composite/composition_functions.go iterates over steps and calls each function runner.
  • The CompositionResult returned to the reconciler already carries Events and Conditions, but no structured step summary.

The implementation would:

  1. Extend CompositionResult with a PipelineSummary field carrying the per-step severity and first message.
  2. After a successful or partial pipeline run, record the summary on the XR via an additional SSA patch in the composite reconciler.
  3. Add the status.pipeline struct to the base XR OpenAPI schema that xcrd.ForCompositeResource generates, so it appears on all XRD-derived CRDs automatically.

Why this matters at scale

Large platform teams operating hundreds of compositions with thousands of XRs frequently receive alerts like "XR foo is Synced=False" and need to investigate quickly. Today that investigation requires:

  1. Checking the XR condition message (only shows the fatal step, or nothing if warnings never caused Synced=False).
  2. Running kubectl get events and hoping the relevant events haven't been pruned.
  3. Querying Crossplane pod logs (requires cluster operator access).
  4. Correlating log lines by XR name and reconcile timestamp.

With status.pipeline, step 1 becomes sufficient for the majority of investigations. This directly reduces mean-time-to-diagnose for composition failures and is consistent with Crossplane's stated observability goals.

Related issues

  • #5643 - Proposal: New Healthy condition for claims and XRs (composed resource health, distinct from pipeline execution health)
  • contributing/guide-observability.md - Crossplane observability developer guide (the "Scenario 1" contract referenced above)
  • internal/xfn/function_runner_metrics.go - existing Prometheus metrics for function execution (aggregated, no per-XR resolution)
  • internal/controller/apiextensions/composite/composition_functions.go - the pipeline step execution loop where step results are available