{"record":{"id":"639eee9cc6d9f81a","repo":"argoproj/argo-workflows","slug":"expected-node-type-s-got-s","errorCode":null,"errorMessage":"expected node type %s, got %s","messagePattern":"expected node type (.+?), got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"workflow/controller/operator.go","lineNumber":3156,"sourceCode":"\t\tif message[0] != node.Message {\n\t\t\twoc.log.WithFields(logging.Fields{\"node\": node.ID, \"message\": message[0]}).Info(ctx, \"node message changed\")\n\t\t\tnode.Message = message[0]\n\t\t\twoc.updated = true\n\t\t}\n\t}\n\tif node.Fulfilled() && node.FinishedAt.IsZero() {\n\t\tnode.FinishedAt = metav1.Time{Time: time.Now().UTC()}\n\t\twoc.log.WithFields(logging.Fields{\"node\": node.ID, \"finishedAt\": node.FinishedAt}).Info(ctx, \"node finished\")\n\t\twoc.controller.tracing.EndNode(ctx, namespacedName, node.ID, node.Phase)\n\t\twoc.updated = true\n\t}\n\twoc.wf.Status.Nodes.Set(ctx, node.ID, *node)\n\treturn node\n}\n\nfunc (woc *wfOperationCtx) getPodByNode(node *wfv1.NodeStatus) (*apiv1.Pod, error) {\n\tif node.Type != wfv1.NodeTypePod {\n\t\treturn nil, fmt.Errorf(\"expected node type %s, got %s\", wfv1.NodeTypePod, node.Type)\n\t}\n\n\tpodName := woc.getPodName(node.Name, wfutil.GetTemplateFromNode(*node))\n\treturn woc.controller.PodController.GetPod(woc.wf.GetNamespace(), podName)\n}\n\nfunc (woc *wfOperationCtx) recordNodePhaseEvent(ctx context.Context, node *wfv1.NodeStatus) {\n\tmessage := fmt.Sprintf(\"%v node %s\", node.Phase, node.Name)\n\tif node.Message != \"\" {\n\t\tmessage = message + \": \" + node.Message\n\t}\n\teventType := apiv1.EventTypeWarning\n\tswitch node.Phase {\n\tcase wfv1.NodeSucceeded, wfv1.NodeRunning:\n\t\teventType = apiv1.EventTypeNormal\n\t}\n\teventConfig := woc.controller.Config.NodeEvents\n\tannotations := map[string]string{","sourceCodeStart":3138,"sourceCodeEnd":3174,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/controller/operator.go#L3138-L3174","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npod, err := woc.getPodByNode(node)\nif err != nil {\n    return err\n}\n// after\nif node.Type != wfv1.NodeTypePod {\n    woc.log.Debug(ctx, \"node has no pod; skipping pod event\", \"type\", node.Type)\n    return nil\n}\npod, err := woc.getPodByNode(node)\nif err != nil {\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// caller-side guard before any pod-based lookup\nif node.Type != wfv1.NodeTypePod {\n    // skip pod lookup / event emission for composite, suspend, http, plugin nodes\n}","typeGuard":"func isPodNode(n *wfv1.NodeStatus) bool {\n    return n != nil && n.Type == wfv1.NodeTypePod\n}","tryCatchPattern":"// Go has no try/catch; recover only if you must around controller helpers\nfunc safePodEvent(woc *wfOperationCtx, ctx context.Context, node *wfv1.NodeStatus) {\n    defer func() { _ = recover() }() // never let event recording crash reconcile\n    if !isPodNode(node) {\n        return\n    }\n    pod, err := woc.getPodByNode(node)\n    if err != nil {\n        woc.log.Debug(ctx, \"no pod for node\", \"err\", err)\n        return\n    }\n    _ = pod\n}","preventionTips":["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"],"tags":["argo-workflows","node-types","pod-lookup","controller"],"backgroundTag":"wrong-node-type-for-pod-lookup","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}