argoproj/argo-workflows · error

unable to download directory %s: %w

Error message

unable to download directory %s: %w

What it means

Load confirmed the key is a directory and delegated to DownloadDirectory, which lists all blobs under the prefix and downloads each; this error wraps any failure from that process. The cause is in the wrapped error — usually a list failure or a per-file download failure.

Source

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

		return fmt.Errorf("unable to determine if %s is a directory: %w", artifact.Azure.Blob, err)
	}

	// It's not a directory and the file doesn't exist, Return the original NoSuchKey error.
	if !isDir && !isEmptyFile {
		return argoerrors.New(argoerrors.CodeNotFound, origErr.Error())
	}

	// When we tried to download the blob as a file, we created an empty file for the
	// blob as a target. We need to delete that empty file so we can re-create as a directory.
	err = os.Remove(path)
	if err != nil {
		return fmt.Errorf("unable to remove attempted file download %s: %w", path, err)
	}

	// It's a directory, so download all of the files.
	err = azblobDriver.DownloadDirectory(ctx, containerClient, artifact, path)
	if err != nil {
		return fmt.Errorf("unable to download directory %s: %w", artifact.Azure.Blob, err)
	}

	return nil
}

// DownloadFile downloads a single file from Azure Blob Storage
func DownloadFile(ctx context.Context, containerClient *container.Client, blobName, path string) error {
	blobClient := containerClient.NewBlobClient(blobName)

	err := os.MkdirAll(filepath.Dir(path), 0755)
	if err != nil {
		return fmt.Errorf("unable to create dir for file %s: %w", path, err)
	}
	outFile, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("unable to create file %s: %w", path, err)
	}
	defer func() {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the nested wrapped error: if 'unable to list blob' fix list permissions; if 'unable to download file' fix that specific blob's access.
  2. Re-upload the directory artifact — the source may be missing or incomplete.
  3. Grant Storage Blob Data Reader on the whole container to the credential.
  4. Retry on transient network errors; consider narrowing the artifact to fewer blobs if large listings time out.
  5. Verify all child blob names are valid local paths (no characters illegal on the container filesystem).

Example fix

// before: artifact re-uploaded partially, one blob 403s
args: [download, dir/]
// after: re-save the directory artifact cleanly
argo submit --output-artifact 'dir => azure blob=dir/'
Defensive patterns

Strategy: retry

Validate before calling

// verify every child is downloadable before bulk download
files, err := driver.ListObjects(ctx, artifact)
if err != nil { return err }
for _, f := range files {
	if _, err := containerClient.NewBlobClient(f).GetProperties(ctx, nil); err != nil { return err }
}

Type guard

func isTransientAzureErr(err error) bool {
	var respErr *azcore.ResponseError
	if errors.As(err, &respErr) {
		switch respErr.StatusCode {
		case 500, 502, 503, 504:
			return true
		}
	}
	return false
}

Try / catch

err := driver.Load(ctx, artifact, path)
if err != nil && isTransientAzureErr(err) {
	// retry with backoff; SDK already retries per-request, add loop-level retry
}

Prevention

When it happens

Trigger: DownloadDirectory fails: ListObjects returns an error (auth/list permission), DownloadFile for any child blob fails (BlobNotFound for missing child, 403, network), or local MkdirAll/download write fails mid-loop.

Common situations: Directory artifact whose prefix contains thousands of blobs and one is deleted/permission-restricted mid-download, SAS token with read but partial permissions, firewall throttling during bulk download.

Related errors


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