argoproj/argo-workflows · error
object %+v is not an unstructured
Error message
object %+v is not an unstructured
What it means
In deleteOffloadedNodesForWorkflow (workflow/controller/controller.go:841), the UID index lookup over the workflow informer returned exactly one object, but its dynamic type is not *unstructured.Unstructured. The informer cache is expected to hold unstructured objects, so this indicates an unexpected object type in the cache. It is a defensive type-assertion failure inside the offloaded-node-status GC.
Source
Thrown at workflow/controller/controller.go:841
}
}
}
}
func (wfc *WorkflowController) deleteOffloadedNodesForWorkflow(ctx context.Context, uid string, versions []string) error {
workflows, err := wfc.wfInformer.GetIndexer().ByIndex(indexes.UIDIndex, uid)
if err != nil {
return err
}
var wf *wfv1.Workflow
logger := logging.RequireLoggerFromContext(ctx)
switch l := len(workflows); l {
case 0:
logger.WithField("uid", uid).Info(ctx, "Workflow missing, probably deleted")
case 1:
un, ok := workflows[0].(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("object %+v is not an unstructured", workflows[0])
}
key := un.GetNamespace() + "/" + un.GetName()
wfc.workflowKeyLock.Lock(key)
defer wfc.workflowKeyLock.Unlock(key)
obj, ok := wfc.getWorkflowByKey(ctx, key)
if !ok {
return fmt.Errorf("failed to get workflow by key after locking")
}
un, ok = obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("object %+v is not an unstructured", obj)
}
wf, err = util.FromUnstructured(un)
if err != nil {
return err
}
View on GitHub (pinned to 35bff19146)
Solutions
- Verify the workflow informer is created with an unstructured decoder (unstructured scheme) as the stock controller does.
- If you embed the controller, pass the same unstructured informer type the controller constructs for itself.
- In tests, seed the indexer with *unstructured.Unstructured objects built via util.FromWorkflow, not typed *wfv1.Workflow.
Example fix
// before (test seeding typed object)
indexer.Add(&wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "wf", Namespace: "ns"}})
// after
un, _ := util.ToUnstructured(&wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "wf", Namespace: "ns"}})
indexer.Add(un) Defensive patterns
Strategy: type-guard
Validate before calling
// Before relying on informer objects, verify cache contents
if !isUnstructured(obj) { log.Fatalf("workflow informer cache holds non-unstructured object: %T", obj) } Type guard
func isUnstructured(obj any) bool {
_, ok := obj.(*unstructured.Unstructured)
return ok
} Try / catch
un, ok := workflows[0].(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("object %+v is not an unstructured", workflows[0])
} Prevention
- Always build the workflow informer with the unstructured decoder
- In tests, seed indexer with util.ToUnstructured output, never typed *wfv1.Workflow
- Diff custom informer wiring against upstream controller setup
When it happens
Trigger: The workflow informer's indexer (UIDIndex) returned an object whose concrete type is not *unstructured.Unstructured — e.g. the informer was configured with a typed decoder or a test injected a *wfv1.Workflow instead of an unstructured object. Practically only reachable with a misconfigured informer or non-standard embedding.
Common situations: Custom controller builds or tests that wire the workflow informer to typed objects instead of unstructured ones; upstream client-go behavior changes in exotic setups.
Related errors
- failed to get workflow by key after locking
- expected no more than 1 workflow, got %d
- unable to convert object %s to configmap when syncing Config
- failed to get pod from informer store: %w
- failed to delete ConfigMap %s: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/6932ab822221fcaa.
Report an issue: GitHub.