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

  1. 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.
  2. Re-fetch the workflow (it may have changed under you) and retry the artifact request — a transient race usually resolves on a fresh read.
  3. Check `argo archive get` / the UI for the correct current workflow UID; if the workflow was deleted, the artifact is no longer addressable.
  4. If nodes are missing from status on large workflows, upgrade Argo Workflows (hydration/offload bugs) and check the controller logs for offload errors.
  5. 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

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


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