argoproj/argo-workflows · error
unable to get artifact and driver; could not get node for %s
Error message
unable to get artifact and driver; could not get node for %s: %w
What it means
During artifact retrieval the Argo Server re-reads the node from wf.Status.Nodes (a second lookup after the artifact itself was found). If the nodeID cannot be resolved in the Workflow status node map, getArtifactAndDriver aborts with 'unable to get artifact and driver; could not get node for <nodeID>'. Because the earlier lookup at line 678 already succeeded, hitting this wrapped error almost always means the node was removed or the workflow status shrank between the two reads (e.g. large-node offloading/hydration issues), or the caller passed an invalid nodeID in a race.
Source
Thrown at server/artifacts/artifact_server.go:703
} else {
art = nodeStatus.Outputs.GetArtifactByName(artifactName)
}
if art == nil {
return nil, nil, fmt.Errorf("artifact not found: %s, isInput=%t, Workflow Status=%+v", artifactName, isInput, wf.Status)
}
// Artifact Location can be defined in various places:
// 1. In the Artifact itself
// 2. Defined by Controller configmap
// 3. Workflow spec defines artifactRepositoryRef which is a ConfigMap which defines the location
// 4. Template defines ArchiveLocation
// 5. Inline Template
var archiveLocation *wfv1.ArtifactLocation
templateNode, err := wf.Status.Nodes.Get(nodeID)
if err != nil {
logger.WithError(err).WithField("nodeID", nodeID).Error(ctx, "was unable to retrieve node")
return nil, nil, fmt.Errorf("unable to get artifact and driver; could not get node for %s: %w", nodeID, err)
}
templateName := util.GetTemplateFromNode(*templateNode)
if templateName != "" {
template := wf.GetTemplateByName(templateName)
if template == nil {
return nil, nil, fmt.Errorf("no template found for name %q associated with nodeID %q", templateName, nodeID)
}
archiveLocation = template.ArchiveLocation // this is case 4
}
if templateName == "" || !archiveLocation.HasLocation() {
ar, arErr := a.artifactRepositories.Get(ctx, wf.Status.ArtifactRepositoryRef) // this should handle cases 2, 3 and 5
if arErr != nil {
return art, nil, arErr
}
archiveLocation = ar.ToArtifactLocation()
}
View on GitHub (pinned to 35bff19146)
Solutions
- Verify the nodeID exists: `argo get <wf> -o json | jq '.status.nodes | keys'` and confirm the node ID used in the artifact request is present.
- Re-fetch the workflow (it may have changed under you) and retry the artifact request — a transient race usually resolves on a fresh read.
- Check `argo archive get` / the UI for the correct current workflow UID; if the workflow was deleted, the artifact is no longer addressable.
- If nodes are missing from status on large workflows, upgrade Argo Workflows (hydration/offload bugs) and check the controller logs for offload errors.
- Confirm the artifact has not been garbage collected (artifactGC) — deleted artifacts/nodes return node lookup failures.
Example fix
// before: caching a nodeID from a deleted workflow
nodeID := oldWf.Status.Nodes[...] // stale
art, err := artifactSvc.GetArtifactFile(ctx, nodeID, ...)
// after: re-fetch the live workflow and validate the node first
wf, err := wfClient.ArgoprojV1alpha1().Workflows(ns).Get(ctx, wfName, metav1.GetOptions{})
if _, ok := wf.Status.Nodes[nodeID]; !ok {
return fmt.Errorf("node %s not present in workflow %s; re-fetch or check GC", nodeID, wfName)
}
art, err := artifactSvc.GetArtifactFile(ctx, nodeID, ...) Defensive patterns
Strategy: validation
Validate before calling
wf, err := wfClient.ArgoprojV1alpha1().Workflows(ns).Get(ctx, wfName, metav1.GetOptions{})
if err != nil { return err }
if _, ok := wf.Status.Nodes[nodeID]; !ok {
return fmt.Errorf("node %q not in workflow %s status; refetch or check artifact GC", nodeID, wfName)
} Type guard
func nodeExists(wf *wfv1.Workflow, nodeID string) bool {
_, ok := wf.Status.Nodes[nodeID]
return ok
} Try / catch
art, err := svc.GetArtifactFile(ctx, nodeID, artName, isInput, nil)
if err != nil {
if strings.Contains(err.Error(), "could not get node for") {
// stale node reference: re-fetch workflow and retry once
return retryWithFreshWorkflow(ctx, wfName, nodeID, artName)
}
return err
} Prevention
- Always resolve node IDs from a freshly fetched Workflow object, not cached/stale UI state.
- Check artifact GC settings — GC'd artifacts leave dangling node references.
- For very large workflows, prefer the archive API which hydrates full status.
- Wrap nodeID extraction in an existence check before requesting artifacts.
When it happens
Trigger: Calling GET artifact endpoints (GetArtifactFile, getArtifact, getArtifactByUID via `argo artifact get` or the UI artifact links) with a pod/node ID that no longer exists in wf.Status.Nodes — e.g. the Workflow was resubmitted/deleted and recreated, artifact GC or status pruning removed the node, the workflow was archived and the node map offloaded without hydration, or a stale UID-based lookup raced with workflow deletion.
Common situations: Fetching artifacts for an old workflow after `argo resubmit`/retry; artifact links opened from a stale UI page while the workflow was being deleted; workflows whose node status was offloaded (very large workflows) and the server failed to hydrate; passing a workflow-level ID instead of a pod node ID.
Related errors
- failed to re-enter working directory %q after staging input
- failed to stat input artifact %q at %s: %w
- failed to create parent directory for artifact %q at %s: %w
- failed to stat artifact path %q at %s: %w
- failed to resolve parent of artifact path %q at %s: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/b560e9b48ac1a331.
Report an issue: GitHub.