argoproj/argo-workflows · error

unable to open blob stream for %s: %w

Error message

unable to open blob stream for %s: %w

What it means

When the initial DownloadStream fails with BlobNotFound and the driver determines the blob is neither a directory nor an empty file, it re-reports the original BlobNotFound wrapped in this message. In practice this means the requested blob genuinely does not exist in the container.

Source

Thrown at workflow/artifacts/azure/azure.go:268

		emptyFile = *response.ContentLength == 0
		// We have a normal file blob, so just return the response body stream
		if !emptyFile {
			return response.Body, nil
		}
	} else if !bloberror.HasCode(origErr, bloberror.BlobNotFound) {
		return nil, fmt.Errorf("unable to open stream for blob %s: %w", artifact.Azure.Blob, origErr)
	}

	isDir, err := azblobDriver.IsDirectory(ctx, artifact)
	if err != nil {
		return nil, fmt.Errorf("unable to test if blob %s is a directory: %w", artifact.Azure.Blob, err)
	}
	if isDir {
		return nil, argoerrors.New(argoerrors.CodeNotImplemented, "Directory Stream capability currently unimplemented for Azure Blob")
	} else if !emptyFile {
		// Not a directory (and not successful retrieval of an empty file), so return
		// the original BlobNotFound error
		return nil, fmt.Errorf("unable to open blob stream for %s: %w", artifact.Azure.Blob, origErr)
	}

	return response.Body, nil
}

// Save saves an artifact to Azure Blob Storage
func (azblobDriver *ArtifactDriver) Save(ctx context.Context, path string, outputArtifact *wfv1.Artifact) error {
	logger := logging.RequireLoggerFromContext(ctx)
	logger.WithField("endpoint", outputArtifact.Azure.Endpoint).
		WithField("container", outputArtifact.Azure.Container).
		WithField("blob", outputArtifact.Azure.Blob).
		Info(ctx, "Saving to Azure Blob Storage")

	isDir, err := file.IsDirectory(path)
	if err != nil {
		return fmt.Errorf("failed to test if %s is a directory: %w", path, err)
	}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Verify the blob exists: az storage blob list --container-name <c> and compare exact names (case-sensitive).
  2. Check the producing workflow step actually completed and saved the artifact under that key.
  3. Add DAG/upstream dependencies so the consuming step runs after the artifact is saved.
  4. Confirm artifact 'path' (save location) and consumer 'key' match.
  5. Check you are pointing at the expected storage account/endpoint (production vs test).

Example fix

// before: consumer has no dependency on producer
tasks:
  - name: consume
    artifacts: [{name: out, from: "{{tasks.produce.outputs.artifacts.out}}"}]
// after: add dependency so artifact exists first
tasks:
  - name: consume
    depends: "produce"
    ...
Defensive patterns

Strategy: validation

Validate before calling

iter := containerClient.NewListBlobsFlatPager(&azblob.ListBlobsFlatOptions{Prefix: &blob})
exists := false
for iter.More() {
  page, err := iter.NextPage(ctx)
  if err != nil { return err }
  for _, b := range page.Segment.BlobItems {
    if *b.Name == blob { exists = true }
  }
}
if !exists { return fmt.Errorf("blob %s does not exist yet", blob) }

Type guard

func isBlobNotFound(err error) bool {
  return bloberror.HasCode(err, bloberror.BlobNotFound)
}

Try / catch

stream, err := driver.OpenStream(ctx, artifact)
if err != nil {
  if isBlobNotFound(errors.Unwrap(err)) {
    // producer hasn't finished or key wrong — check workflow deps
  }
  return err
}

Prevention

When it happens

Trigger: OpenStream where the blob key is wrong, the upstream step never produced/saved the artifact, or the artifact was uploaded under a different prefix/container.

Common situations: Typo in artifact path; consumer workflow runs before producer saved the artifact (missing depends/dag dependency); archived artifact deleted; account/container mismatch.

Related errors


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