{"record":{"id":"1b34938013352132","repo":"argoproj/argo-workflows","slug":"unable-to-test-if-blob-s-is-a-directory-w","errorCode":null,"errorMessage":"unable to test if blob %s is a directory: %w","messagePattern":"unable to test if blob (.+?) is a directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/azure/azure.go","lineNumber":261,"sourceCode":"\t// Attempt the download. If it fails with a BlobNotFound error, or succeeds but with\n\t// a content length of 0, then it could be that we're attempting to stream a directory.\n\t// Check if the blob represents a directory and return an error if so. If not, then\n\t// return either the original BlobNotFound error or the empty file stream.\n\temptyFile := false\n\tresponse, origErr := blobClient.DownloadStream(ctx, nil)\n\tif origErr == nil {\n\t\temptyFile = *response.ContentLength == 0\n\t\t// We have a normal file blob, so just return the response body stream\n\t\tif !emptyFile {\n\t\t\treturn response.Body, nil\n\t\t}\n\t} else if !bloberror.HasCode(origErr, bloberror.BlobNotFound) {\n\t\treturn nil, fmt.Errorf(\"unable to open stream for blob %s: %w\", artifact.Azure.Blob, origErr)\n\t}\n\n\tisDir, err := azblobDriver.IsDirectory(ctx, artifact)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to test if blob %s is a directory: %w\", artifact.Azure.Blob, err)\n\t}\n\tif isDir {\n\t\treturn nil, argoerrors.New(argoerrors.CodeNotImplemented, \"Directory Stream capability currently unimplemented for Azure Blob\")\n\t} else if !emptyFile {\n\t\t// Not a directory (and not successful retrieval of an empty file), so return\n\t\t// the original BlobNotFound error\n\t\treturn nil, fmt.Errorf(\"unable to open blob stream for %s: %w\", artifact.Azure.Blob, origErr)\n\t}\n\n\treturn response.Body, nil\n}\n\n// Save saves an artifact to Azure Blob Storage\nfunc (azblobDriver *ArtifactDriver) Save(ctx context.Context, path string, outputArtifact *wfv1.Artifact) error {\n\tlogger := logging.RequireLoggerFromContext(ctx)\n\tlogger.WithField(\"endpoint\", outputArtifact.Azure.Endpoint).\n\t\tWithField(\"container\", outputArtifact.Azure.Container).\n\t\tWithField(\"blob\", outputArtifact.Azure.Blob).","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/azure/azure.go#L243-L279","documentation":"After OpenStream hits BlobNotFound or an empty file, it calls IsDirectory to check whether the blob name is actually a directory (prefix). If IsDirectory itself fails (listing the prefix errors), the driver wraps that failure with this message instead of deciding file-vs-directory.","triggerScenarios":"OpenStream on a missing/empty blob when the subsequent ListBlob flat-metadata call inside IsDirectory fails: auth errors, container not found, throttling, network failures.","commonSituations":"Same credential problems as the original download but only surfacing on the fallback path; ADLS Gen2 accounts where prefix listing needs extra permissions; transient 429/503 during listing.","solutions":["Fix the cause of the listing failure shown by the wrapped error (credentials, container existence, permissions).","Ensure the account key/identity has Storage Blob Data Reader (list) rights, not just read of a single blob.","Verify container name and endpoint in the artifact spec.","Retry on transient storage errors.","If the artifact should really exist, check the producing step's blob name matches exactly (no stray prefix)."],"exampleFix":"// before: identity lacks list permission\nrole: \"Storage Blob Data Reader (read only single blob)\"\n// after: grant data-plane list access\nrole: \"Storage Blob Data Reader\"  # includes list on container","handlingStrategy":"retry","validationCode":"props, err := containerClient.NewBlockBlobClient(blob).GetProperties(ctx, nil)\nif err != nil && !bloberror.HasCode(err, bloberror.BlobNotFound) {\n  return fmt.Errorf(\"storage backend unhealthy: %w\", err)\n}","typeGuard":"func isTransientAzureErr(err error) bool {\n  var re *azcore.ResponseError\n  return errors.As(err, &re) && (re.StatusCode == 429 || re.StatusCode >= 500)\n}","tryCatchPattern":"err := driver.OpenStream(ctx, artifact)\n// wrap call\nif err != nil && strings.Contains(err.Error(), \"unable to test if blob\") {\n  if isTransientAzureErr(errors.Unwrap(err)) {\n    // exponential backoff retry\n  }\n}","preventionTips":["Grant the storage identity data-plane LIST rights (Storage Blob Data Reader+).","Retry transient 429/5xx storage errors with backoff.","Check container existence before artifact reads."],"tags":["azure","blob-storage","artifact","permissions"],"backgroundTag":"azure-blob-list-failed","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}