Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Infrastructure as Code By James Joyner IV · · 9 min read

IaC Error Guide: 'cannot resolve' Crossplane Composite Not Ready

Fix Crossplane composite resources stuck not Ready: diagnose unhealthy providers, bad ProviderConfig credentials, composition selectors, and patch errors.

  • #iac
  • #troubleshooting
  • #errors
  • #crossplane

Overview

This error happens when a Crossplane claim or composite resource (XR) never reaches READY=True because one of the managed resources it composes cannot be created or reconciled. The composite reports SYNCED=True (the composition rendered) but READY=False, and the provider that owns the underlying managed resource cannot resolve it against the cloud API. The whole claim hangs in a pending state.

You will see this in the claim or composite status conditions:

$ kubectl describe xpostgresqlinstance my-db-abc12

Status:
  Conditions:
    Type     Reason                Status   Message
    Synced   ReconcileSuccess      True
    Ready    Creating              False
Events:
  Warning  CannotResolveResourceReferences  cannot resolve references:
    referenced field was empty (referenced resource may not yet be ready)

It occurs because Crossplane reconciliation is layered: the claim binds to a composite, the composite renders managed resources via a Composition, and each managed resource is reconciled by a provider against a real cloud API. A failure at any layer — an unhealthy provider, bad credentials, a selector that matches nothing, or a cloud API rejection — surfaces as a composite that is synced but never ready.

Symptoms

  • A claim stays READY=False indefinitely while SYNCED=True.
  • kubectl get managed shows a resource with SYNCED=True but READY=False.
  • Events show CannotResolveResourceReferences or cannot resolve.
  • kubectl get providers shows a provider that is not HEALTHY.
kubectl get xpostgresqlinstance
NAME            SYNCED   READY   COMPOSITION              AGE
my-db-abc12     True     False   xpostgresqlinstances...  6m
kubectl get managed
NAME                                       READY   SYNCED   EXTERNAL-NAME   AGE
rdsinstance.database.aws.../my-db-abc12-x   False   True                    6m

Common Root Causes

1. The provider is not installed or not Healthy

If the provider package is not HEALTHY, it cannot reconcile any managed resource of its kind.

kubectl get providers
NAME                  INSTALLED   HEALTHY   PACKAGE                                  AGE
provider-aws-rds      True        False     xpkg.upbound.io/.../provider-aws-rds...  9m

HEALTHY=False means the provider pod is crashlooping or its CRDs failed to install — nothing of that kind will reconcile.

2. ProviderConfig credentials are wrong or missing

The managed resource references a ProviderConfig, but its credentials secret is absent or invalid, so every cloud call is rejected.

kubectl get providerconfig
kubectl describe rdsinstance my-db-abc12-x | grep -A3 Events
NAME      AGE
default   9m

Events:
  Warning  CannotConnectToProvider  cannot get terminal client:
    InvalidClientTokenId: The security token included in the request is invalid

The credentials in the ProviderConfig secret are invalid, so the provider cannot authenticate to AWS.

3. Composition selector matches nothing

The composite’s compositionRef/compositionSelector does not match an installed Composition, so no managed resources are rendered.

kubectl get composition
kubectl describe xpostgresqlinstance my-db-abc12 | grep -i composition
NAME                              XR-KIND                XR-APIVERSION
xpostgresqlinstances.gcp.example  XPostgreSQLInstance    database.example.org/v1alpha1

Composition Ref:   <none>

The only Composition present targets GCP, but the claim selects an AWS one that does not exist — so nothing is composed.

4. Managed resource stuck SYNCED=True READY=False on a cloud API error

The composition rendered correctly and the provider is healthy, but the cloud API rejected the create (quota, invalid parameter, name conflict).

kubectl describe rdsinstance my-db-abc12-x | grep -A5 'Conditions\|Events'
  Type     Reason            Status   Message
  Synced   ReconcileSuccess  True
  Ready    Unavailable       False
Events:
  Warning  CannotCreateExternalResource  create failed:
    InvalidParameterValue: The parameter MasterUserPassword is not a valid password
    because it is shorter than 8 characters.

SYNCED=True (Crossplane did its part) but READY=False because AWS rejected the parameter.

5. Missing CRDs (XRD not established)

The CompositeResourceDefinition (XRD) is not established, so the claim’s CRD does not exist and the composite cannot be created.

kubectl get xrd
kubectl get crd | grep postgresql
NAME                                       ESTABLISHED   OFFERED   AGE
xpostgresqlinstances.database.example.org  False         True      8m

ESTABLISHED=False means the XRD failed to register its CRDs — claims of that kind will not reconcile.

6. Patch / transform error in the Composition

A patch references a field path that does not exist or a transform fails, so the rendered managed resource is malformed.

kubectl get events --field-selector reason=ComposeResources --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON             OBJECT                          MESSAGE
30s         Warning   ComposeResources   xpostgresqlinstance/my-db-...   cannot compose resources:
  cannot render composed resource from resource template at index 0:
  cannot resolve patch: cannot get value at field path spec.parameters.storageGb:
  no such field

The patch reads spec.parameters.storageGb, but the claim sets storageGB — the field path mismatch breaks composition.

Diagnostic Workflow

Step 1: Read the composite/claim status conditions

kubectl describe xpostgresqlinstance <NAME>

Note whether Synced and Ready are true/false and read the Message. Synced=False points at composition/XRD problems; Synced=True, Ready=False points at the managed resource layer.

Step 2: Confirm providers and ProviderConfigs are healthy

kubectl get providers
kubectl get providerconfig

Any provider that is not HEALTHY, or a missing ProviderConfig, blocks every managed resource it owns (causes 1 and 2).

Step 3: Drill into the managed resource

kubectl get managed
kubectl describe <managed-resource>.<group> <NAME>

Read the Conditions and Events. A cloud API error here (cause 4) names the exact rejected parameter or quota.

Step 4: Verify the XRD and Composition wiring

kubectl get xrd
kubectl get composition
kubectl get crd | grep <kind>

ESTABLISHED=False on the XRD (cause 5) or a selector that matches no Composition (cause 3) explains a composite that never renders managed resources.

Step 5: Inspect events for compose/patch failures

kubectl get events --sort-by=.lastTimestamp | tail -20

ComposeResources warnings with no such field or cannot resolve patch reveal patch/transform errors (cause 6) that you fix in the Composition or claim spec.

Example Root Cause Analysis

A developer creates a PostgreSQLInstance claim. It reports SYNCED=True but never becomes ready, and the underlying RDS instance shows READY=False.

The claim status itself is unremarkable, so drill into the managed resource:

kubectl describe rdsinstance my-db-abc12-x | grep -A4 Events
Events:
  Warning  CannotCreateExternalResource  create failed:
    InvalidParameterValue: The parameter MasterUserPassword is not a valid password
    because it is shorter than 8 characters.

The provider is healthy and the composition rendered fine — SYNCED=True. The failure is purely at the cloud API: AWS rejected the short master password that the Composition derived from the claim’s passwordSecretRef. Checking the referenced secret confirms it holds a 6-character value.

The fix is to supply a valid password in the referenced secret and let Crossplane re-reconcile:

kubectl create secret generic db-conn \
  --from-literal=password='Sup3rSecret99' \
  --dry-run=client -o yaml | kubectl apply -f -
secret/db-conn configured

Crossplane re-reconciles the rdsinstance on its next loop. With a valid password the AWS create succeeds, the managed resource flips to READY=True, and the composite — and therefore the claim — becomes ready. The durable fix is to validate secret contents (length/format) before they feed a Composition, so a bad input fails fast instead of stalling reconciliation.

Prevention Best Practices

  • Gate rollouts on provider health: alert when kubectl get providers shows any provider not HEALTHY, since that silently blocks every managed resource it owns. Codify provider and ProviderConfig manifests alongside your infrastructure-as-code guides.
  • Validate ProviderConfig credentials immediately after install with a throwaway managed resource, so invalid tokens surface before real claims depend on them.
  • Keep field paths in Composition patches in lock-step with the XRD’s openAPIV3Schema; a renamed parameter is the most common cannot resolve patch cause.
  • Wait for XRDs to report ESTABLISHED=True before applying claims in automation, and order XRD/Composition installs ahead of claims in your GitOps sync waves.
  • Surface managed-resource conditions in dashboards, not just claim readiness, so a cloud API rejection deep in the stack is visible at a glance.
  • When a claim hangs and you need a fast read of the layered status, the free incident assistant can summarize the provider/composition/managed-resource conditions into the likely cause.

Quick Command Reference

# Composite / claim readiness and conditions
kubectl get xpostgresqlinstance
kubectl describe xpostgresqlinstance <NAME>

# Provider and credential health
kubectl get providers
kubectl get providerconfig

# Managed resources and their cloud-API errors
kubectl get managed
kubectl describe <managed-resource>.<group> <NAME>

# Composition / XRD wiring
kubectl get xrd
kubectl get composition
kubectl get crd | grep <kind>

# Compose / patch failures
kubectl get events --sort-by=.lastTimestamp | tail -20

Conclusion

A Crossplane composite that is synced but never ready means one layer of the claim -> composite -> managed-resource -> cloud chain failed to resolve. The usual root causes:

  1. The provider is not installed or not HEALTHY.
  2. The ProviderConfig credentials are missing or invalid.
  3. The composition selector matches no installed Composition.
  4. A managed resource is SYNCED=True, READY=False because the cloud API rejected it.
  5. The XRD is not ESTABLISHED, so the claim’s CRD never registered.
  6. A patch/transform in the Composition references a field path that does not exist.

Read the conditions top-down — claim, composite, managed resource — and let the layer that is READY=False point you at the provider, credentials, composition, or cloud-API fix.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.