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

  1. Check `node.Type == wfv1.NodeTypePod` before calling getPodByNode and skip non-pod nodes (no event/pod lookup is applicable for them)
  2. For HTTP and plugin templates, do not expect pod-level events — they execute in the shared agent pod; use the node status/taskset instead
  3. 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
  4. 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

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


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/639eee9cc6d9f81a. Report an issue: GitHub.