argoproj/argo-workflows · error

unable to list blob %s in Azure Storage: %w

Error message

unable to list blob %s in Azure Storage: %w

What it means

DownloadDirectory lists all blobs under the artifact's blob prefix via ListObjects before downloading them. This error wraps a failure from that listing — either the container client creation inside ListObjects or a pager.NextPage API failure. The real cause is in the wrapped error.

Source

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

			logger.WithFatal().WithError(closeErr).Warn(ctx, "unable to close file")
		}
	}()

	_, err = blobClient.DownloadFile(ctx, outFile, nil)
	return err
}

// DownloadDirectory downloads all of the files starting with the named blob prefix into a local directory.
func (azblobDriver *ArtifactDriver) DownloadDirectory(ctx context.Context, containerClient *container.Client, artifact *wfv1.Artifact, path string) error {
	logger := logging.RequireLoggerFromContext(ctx)
	logger.WithField("endpoint", artifact.Azure.Endpoint).
		WithField("container", artifact.Azure.Container).
		WithField("blob", artifact.Azure.Blob).
		Info(ctx, "Downloading directory from Azure Blob Storage")

	files, err := azblobDriver.ListObjects(ctx, artifact)
	if err != nil {
		return fmt.Errorf("unable to list blob %s in Azure Storage: %w", artifact.Azure.Blob, err)
	}

	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 {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Check the wrapped error code: 403 => grant List (Storage Blob Data Reader or SAS 'l' permission); 404 container => fix container name.
  2. Verify endpoint and credentials by running az storage blob list with the same key locally.
  3. If the wrapped error is a client-creation error, fix the endpoint/accountKey per the nested message.
  4. Retry on transient network/timeout errors; narrow the artifact prefix to reduce listing size.
  5. Ensure network egress from workflow pods to <account>.blob.core.windows.net:443 is allowed.

Example fix

// before
permissions: r
// after: SAS with list
az storage blob generate-sas --permissions rl --https-only ...
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: list with the same prefix before running the workflow
_, err := containerClient.NewListBlobsFlatPager(&azblob.ListBlobsFlatOptions{Prefix: &prefix}).NextPage(ctx)
if err != nil { return fmt.Errorf("list precheck failed: %w", err) }

Type guard

func isSasMissingListPerm(err error) bool {
	var respErr *azcore.ResponseError
	return errors.As(err, &respErr) && respErr.ErrorCode == "AuthorizationFailure"
}

Try / catch

err := driver.Load(ctx, artifact, path)
if strings.Contains(err.Error(), "unable to list blob") {
	var respErr *azcore.ResponseError
	if errors.As(err, &respErr) {
		if respErr.ErrorCode == "AuthorizationFailure" { /* add List permission */ }
		if respErr.StatusCode >= 500 || respErr.StatusCode == 408 { /* retry */ }
	}
}

Prevention

When it happens

Trigger: ListObjects' list call fails: 403 AuthorizationFailure (SAS/identity lacks List), ContainerNotFound (wrong container name), InvalidQueryParameterValue/other REST errors, context deadline exceeded on huge prefixes, or newAzureContainerClient fails (bad endpoint/key).

Common situations: SAS token generated with only read ('r') but not list ('l') permission, typo in artifact.Azure.Container, storage account firewall blocking the cluster egress, listing a prefix with millions of blobs until timeout.

Related errors


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