argoproj/argo-workflows · error

unable to download file %s: %w

Error message

unable to download file %s: %w

What it means

While downloading a directory artifact, each child blob is fetched with DownloadFile into its mapped local path; this error wraps the failure of one specific child download. It is the per-file counterpart of the directory download — the nested error carries the actual cause (Azure-side or local FS).

Source

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

	}

	err = os.MkdirAll(path, 0755)
	if err != nil {
		return fmt.Errorf("unable to create local directory %s: %w", path, err)
	}

	for _, file := range files {
		// For ADLS Gen 2 accounts, we'll see a file whose name matches the directory. Skip it.
		if file == artifact.Azure.Blob {
			continue
		}

		relKeyPath := strings.TrimPrefix(file, artifact.Azure.Blob)
		localPath := filepath.Join(path, relKeyPath)

		err = DownloadFile(ctx, containerClient, file, localPath)
		if err != nil {
			return fmt.Errorf("unable to download file %s: %w", localPath, err)
		}
	}
	return nil
}

// OpenStream opens a stream reader for an artifact from Azure Blob Storage
func (azblobDriver *ArtifactDriver) OpenStream(ctx context.Context, artifact *wfv1.Artifact) (io.ReadCloser, error) {
	logger := logging.RequireLoggerFromContext(ctx)
	logger.WithField("endpoint", artifact.Azure.Endpoint).
		WithField("container", artifact.Azure.Container).
		WithField("blob", artifact.Azure.Blob).
		Info(ctx, "Streaming from Azure Blob Storage")
	containerClient, err := azblobDriver.newAzureContainerClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("unable to create Azure Blob Container client: %w", err)
	}

	blobClient := containerClient.NewBlockBlobClient(artifact.Azure.Blob)

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the nested wrapped error: Azure code => fix access/re-upload that blob; os error => fix local path/permissions/disk.
  2. Re-upload the directory artifact to ensure all children exist and are consistent.
  3. If a blob name contains path-hostile characters, sanitize names when saving the artifact.
  4. Increase disk/emptyDir size if ENOSPC occurred mid-download.
  5. Retry the workflow — mid-download transient network failures are often one-shot.

Example fix

// before: producer deletes blobs during download
step writes output -> workflow downloads same prefix concurrently
// after: snapshot first (copy or version prefix)
upload to blob: outputs/run-42/ then download outputs/run-42/
Defensive patterns

Strategy: retry

Validate before calling

// check disk headroom before bulk download
var st syscall.Statfs_t
if err := syscall.Statfs(filepath.Dir(path), &st); err == nil {
	avail := int64(st.Bavail) * int64(st.Bsize)
	if avail < minRequiredBytes { return fmt.Errorf("insufficient disk: %d", avail) }
}

Type guard

func isRetryableDownloadErr(err error) bool {
	var respErr *azcore.ResponseError
	if errors.As(err, &respErr) {
		return respErr.StatusCode == 500 || respErr.StatusCode == 503 || respErr.StatusCode == 504
	}
	return errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF)
}

Try / catch

err := driver.Load(ctx, artifact, path)
if err != nil {
	if isRetryableDownloadErr(err) {
		time.Sleep(backoff)
		err = driver.Load(ctx, artifact, path)
	}
}

Prevention

When it happens

Trigger: DownloadFile(childBlob, localPath) fails: child blob deleted between list and download (BlobNotFound), 403 on that blob, transient network failure mid-stream, or local os.Create/MkdirAll failure at the derived localPath (permissions, disk full, illegal filename from blob name).

Common situations: Blob names with characters invalid on Linux paths in directory artifacts, concurrent producer writing/deleting blobs while the workflow downloads, ephemeral disk filling during large directory downloads, RBAC that allows some blobs but not others (per-path policies in ADLS Gen2).

Related errors


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