Crossplane Error: 'cannot establish control of object: existing object is not controlled by this composite resource' — Cause, Fix, and Troubleshooting Guide
Fix Crossplane 'cannot establish control of object: existing object is not controlled by this composite resource' when two XRs or an adopted resource collide.
- #iac
- #infrastructure-as-code
- #crossplane
- #troubleshooting
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Crossplane enforces single ownership of every managed resource (MR). Each MR carries a controller owner reference pointing back to exactly one composite resource (XR). When a composition tries to render or claim ownership of an MR that already has a different controller owner reference — or no Crossplane owner reference at all — the reconciler refuses to take control and reports:
cannot establish control of object: existing object is not controlled by this composite resource
You will see it in the XR or claim events, and the affected MR stays SYNCED=False. The reconcile loop retries forever, so the resource never progresses. It shows up in three situations: two Compositions/XRs both render an MR with the same external-name or deterministic name; someone tried to “adopt” a hand-created cloud resource into Crossplane; or an XR was recreated (new UID) while the old MR still references the previous owner UID.
$ kubectl describe bucket my-app-bucket
Events:
Type Reason Message
---- ------ -------
Warning CannotInitializeManagedResource cannot establish control of object:
existing object is not controlled by this composite resource
Symptoms
- An MR (
Bucket,RDSInstance,Subnetwork, etc.) is stuckSYNCED=False,READY=False. - XR/claim events show
cannot establish control of object. - Two XRs list the same
external-nameannotation on their MRs. - The MR’s
.metadata.ownerReferences[].uiddoes not match the current XR’s.metadata.uid. - Deleting and recreating the claim did not help — the underlying MR persisted.
Common Root Causes
1. Two Compositions render an MR with the same deterministic name
If two XRs patch the same crossplane.io/external-name (or produce the same metadata.name), the second XR’s MR collides with the first XR’s object.
# Both compositions patch external-name to a fixed value — collision
- type: FromCompositeFieldPath
fromFieldPath: spec.bucketName # same value for two claims
toFieldPath: metadata.annotations[crossplane.io/external-name]
2. Adopting a pre-existing cloud resource without the right owner reference
Setting crossplane.io/external-name to an existing bucket makes the provider observe it, but the in-cluster MR object may already exist from a prior import owned by another XR.
3. XR was recreated and the MR kept the old owner UID
Deleting a claim with Orphan deletion policy leaves the MR behind. A new claim with the same name gets a new UID, but the orphaned MR still references the old UID.
ownerReferences:
- uid: 9f1c-OLD-UID # points at the deleted XR, not the new one
4. A managed resource created directly (not through a composition)
Someone applied a Bucket MR by hand, then wrote a Composition that renders the same object. The hand-applied object has no composite owner reference at all.
5. Two Crossplane control planes or namespaces sharing one cloud account
Two clusters both reconciling the same external-name fight over the same cloud object; whichever wrote the owner reference first wins, the other errors.
How to Diagnose
Identify the conflicting MR and its owner
kubectl get managed
kubectl describe bucket my-app-bucket
Compare owner UID against the current XR
# UID the MR thinks owns it
kubectl get bucket my-app-bucket -o jsonpath='{.metadata.ownerReferences[*].uid}{"\n"}'
# UID of the XR that is trying to control it
kubectl get xbuckets.example.com -o jsonpath='{.items[*].metadata.uid}{"\n"}'
If these differ, you have a stale/foreign owner reference.
Find duplicate external-names
kubectl get managed -o custom-columns=\
'NAME:.metadata.name,EXT:.metadata.annotations.crossplane\.io/external-name'
Two rows with the same EXT value are the collision.
Watch the reconcile in real time
crossplane beta trace xbuckets.example.com/my-app -o wide
kubectl -n crossplane-system logs deploy/provider-aws-s3 -f | grep establish
Fixes
Resolve a stale owner reference (recreated XR)
If the MR is genuinely meant for the new XR, remove the dangling owner reference so the current XR can adopt it:
kubectl patch bucket my-app-bucket --type=json \
-p='[{"op":"remove","path":"/metadata/ownerReferences"}]'
On the next reconcile the correct XR sets its own owner reference. Only do this once you have confirmed the old XR is gone.
Break a duplicate-name collision
Make each XR produce a unique external-name. Include the XR name or a hash rather than a fixed field:
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: spec.bucketName
- fromFieldPath: metadata.uid
strategy: string
string:
fmt: "%s-%.8s"
toFieldPath: metadata.annotations[crossplane.io/external-name]
Cleanly re-adopt a hand-created resource
Delete the un-owned MR object from the cluster (with Orphan so the cloud resource survives), then let the composition recreate it with proper ownership:
kubectl patch bucket my-app-bucket \
-p '{"spec":{"deletionPolicy":"Orphan"}}' --type=merge
kubectl delete bucket my-app-bucket
# composition re-renders the MR, observes the existing cloud bucket, owns it
Stop two control planes fighting
Give each control plane a distinct naming prefix or a dedicated cloud account/project so their external-name values can never overlap.
What to Watch Out For
- Never patch a fixed, shared value into
crossplane.io/external-nameacross multiple claims — always incorporate a unique component. - Prefer
deletionPolicy: Deleteunless you have a deliberate import/adopt workflow; orphans are the most common source of this error. - When importing existing infrastructure, import into exactly one XR and document it.
- After recreating an XR, verify owner UIDs match before assuming the platform will self-heal.
- Keep one Crossplane control plane authoritative per cloud account to avoid cross-cluster ownership races.
Related Guides
- Crossplane Cannot Resolve References
- Crossplane Composition Not Ready
- Crossplane ProviderConfig Not Found
Get 500 Battle-Tested DevOps AI Prompts — Free
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.