What happened?
Generated controllers build the reconciler options in pkg/pipeline/templates/controller.go.tmpl without managed.WithDeterministicExternalName, so deterministicExternalName is false for every upjet resource.
When a create is interrupted after crossplane.io/external-create-pending is set but before -succeeded/-failed is recorded (for example the provider pod restarts during an async create), crossplane-runtime treats the create as incomplete. With deterministicExternalName false it refuses to retry, and the MR is stuck indefinitely on:
cannot determine creation result - remove the crossplane.io/external-create-pending annotation if it is safe to proceed
crossplane-runtime already handles the safe case: when deterministicExternalName is true it logs "proceeding due to deterministic external name" and continues. Resources with a user-supplied external name (everything except config.IdentifierFromProvider) are deterministic, so the retry is safe and would not leak a resource. Because upjet never opts in, the only recovery is deleting the annotation by hand, which is not viable for GitOps-managed clusters.
How can we reproduce it?
- Pick a resource with a user-supplied external name (
config.NameAsIdentifier or config.TemplatedStringAsIdentifier).
- Create an instance, then restart the provider pod while the async create is in flight, so
external-create-pending ends up as the newest of the three external-create-* annotations.
- The MR stays
Synced=False with "cannot determine creation result" and never retries until the annotation is removed manually.
Suggested fix
Add managed.WithDeterministicExternalName(deterministic) to the opts in controller.go.tmpl, with deterministic derived from the resource config: false for config.IdentifierFromProvider (provider-assigned id), true for user-supplied external names.
What happened?
Generated controllers build the reconciler options in
pkg/pipeline/templates/controller.go.tmplwithoutmanaged.WithDeterministicExternalName, sodeterministicExternalNameis false for every upjet resource.When a create is interrupted after
crossplane.io/external-create-pendingis set but before-succeeded/-failedis recorded (for example the provider pod restarts during an async create), crossplane-runtime treats the create as incomplete. WithdeterministicExternalNamefalse it refuses to retry, and the MR is stuck indefinitely on:crossplane-runtime already handles the safe case: when
deterministicExternalNameis true it logs "proceeding due to deterministic external name" and continues. Resources with a user-supplied external name (everything exceptconfig.IdentifierFromProvider) are deterministic, so the retry is safe and would not leak a resource. Because upjet never opts in, the only recovery is deleting the annotation by hand, which is not viable for GitOps-managed clusters.How can we reproduce it?
config.NameAsIdentifierorconfig.TemplatedStringAsIdentifier).external-create-pendingends up as the newest of the threeexternal-create-*annotations.Synced=Falsewith "cannot determine creation result" and never retries until the annotation is removed manually.Suggested fix
Add
managed.WithDeterministicExternalName(deterministic)to theoptsincontroller.go.tmpl, withdeterministicderived from the resource config: false forconfig.IdentifierFromProvider(provider-assigned id), true for user-supplied external names.