argoproj/argo-workflows · warning
expected node type %s, got %s
Error message
expected node type %s, got %s
What it means
`getPodByNode` (workflow/controller/operator.go:3156) maps a workflow node back to the Kubernetes pod that ran it, which only makes sense for nodes of type `Pod`. If the node's type is anything else (Container, Steps, StepGroup, DAG, Suspend, Skipped, TaskGroup, HTTP, Plugin, Retry...), there is no pod to find and the function returns this error. It is surfaced by callers like `recordNodePhaseEvent` when emitting pod-scoped events for a node.
Source
Thrown at workflow/controller/operator.go:3156
if message[0] != node.Message {
woc.log.WithFields(logging.Fields{"node": node.ID, "message": message[0]}).Info(ctx, "node message changed")
node.Message = message[0]
woc.updated = true
}
}
if node.Fulfilled() && node.FinishedAt.IsZero() {
node.FinishedAt = metav1.Time{Time: time.Now().UTC()}
woc.log.WithFields(logging.Fields{"node": node.ID, "finishedAt": node.FinishedAt}).Info(ctx, "node finished")
woc.controller.tracing.EndNode(ctx, namespacedName, node.ID, node.Phase)
woc.updated = true
}
woc.wf.Status.Nodes.Set(ctx, node.ID, *node)
return node
}
func (woc *wfOperationCtx) getPodByNode(node *wfv1.NodeStatus) (*apiv1.Pod, error) {
if node.Type != wfv1.NodeTypePod {
return nil, fmt.Errorf("expected node type %s, got %s", wfv1.NodeTypePod, node.Type)
}
podName := woc.getPodName(node.Name, wfutil.GetTemplateFromNode(*node))
return woc.controller.PodController.GetPod(woc.wf.GetNamespace(), podName)
}
func (woc *wfOperationCtx) recordNodePhaseEvent(ctx context.Context, node *wfv1.NodeStatus) {
message := fmt.Sprintf("%v node %s", node.Phase, node.Name)
if node.Message != "" {
message = message + ": " + node.Message
}
eventType := apiv1.EventTypeWarning
switch node.Phase {
case wfv1.NodeSucceeded, wfv1.NodeRunning:
eventType = apiv1.EventTypeNormal
}
eventConfig := woc.controller.Config.NodeEvents
annotations := map[string]string{View on GitHub (pinned to 35bff19146)
Solutions
- Check `node.Type == wfv1.NodeTypePod` before calling getPodByNode and skip non-pod nodes (no event/pod lookup is applicable for them)
- For HTTP and plugin templates, do not expect pod-level events — they execute in the shared agent pod; use the node status/taskset instead
- For container-set (emissary) nodes whose type is Container within a pod, route through the pod controller using the node's pod name derived from getPodName rather than this API
- If you are a developer: make recordNodePhaseEvent tolerant — log a debug message and return nil instead of propagating the error for non-pod nodes
Example fix
// before
pod, err := woc.getPodByNode(node)
if err != nil {
return err
}
// after
if node.Type != wfv1.NodeTypePod {
woc.log.Debug(ctx, "node has no pod; skipping pod event", "type", node.Type)
return nil
}
pod, err := woc.getPodByNode(node)
if err != nil {
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// caller-side guard before any pod-based lookup
if node.Type != wfv1.NodeTypePod {
// skip pod lookup / event emission for composite, suspend, http, plugin nodes
} Type guard
func isPodNode(n *wfv1.NodeStatus) bool {
return n != nil && n.Type == wfv1.NodeTypePod
} Try / catch
// Go has no try/catch; recover only if you must around controller helpers
func safePodEvent(woc *wfOperationCtx, ctx context.Context, node *wfv1.NodeStatus) {
defer func() { _ = recover() }() // never let event recording crash reconcile
if !isPodNode(node) {
return
}
pod, err := woc.getPodByNode(node)
if err != nil {
woc.log.Debug(ctx, "no pod for node", "err", err)
return
}
_ = pod
} Prevention
- Always branch on node.Type before pod-related logic
- Remember HTTP/plugin nodes run in the agent pod — no per-node pod exists
- In controller code, degrade event-recording failures to logs rather than errors
- Filter node maps by type when iterating Status.Nodes
When it happens
Trigger: Calling getPodByNode on a node whose `Type` is not `wfv1.NodeTypePod`: e.g. recording phase events for a container-set (emissary) node, a DAG/steps composite node, a suspended node, or an HTTP/plugin node (which run in the agent pod, not their own pod). Any code path that iterates nodes and assumes pod-backed without checking node type.
Common situations: Users see this indirectly as 'Error recording event' / missing pod events for non-pod nodes in the UI or controller logs; plugin/HTTP template authors notice no pod events are emitted; developers writing new controller features that call getPodByNode on nodes from mixed node lists.
Related errors
- critical error; unable to find %s
- no Node found by the name of %s; wf.Status.Nodes=%+v
- no Retry Node found by the name of %s; wf.Status.Nodes=%+v
- could not marshal data in transformation: %w
- cannot fetch workflow spec without workflowTemplateRef
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/639eee9cc6d9f81a.
Report an issue: GitHub.