{"record":{"id":"bc22a0fc19b3c329","repo":"argoproj/argo-workflows","slug":"unable-to-download-directory-s-w","errorCode":null,"errorMessage":"unable to download directory %s: %w","messagePattern":"unable to download directory (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/azure/azure.go","lineNumber":165,"sourceCode":"\t\treturn fmt.Errorf(\"unable to determine if %s is a directory: %w\", artifact.Azure.Blob, err)\n\t}\n\n\t// It's not a directory and the file doesn't exist, Return the original NoSuchKey error.\n\tif !isDir && !isEmptyFile {\n\t\treturn argoerrors.New(argoerrors.CodeNotFound, origErr.Error())\n\t}\n\n\t// When we tried to download the blob as a file, we created an empty file for the\n\t// blob as a target. We need to delete that empty file so we can re-create as a directory.\n\terr = os.Remove(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to remove attempted file download %s: %w\", path, err)\n\t}\n\n\t// It's a directory, so download all of the files.\n\terr = azblobDriver.DownloadDirectory(ctx, containerClient, artifact, path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to download directory %s: %w\", artifact.Azure.Blob, err)\n\t}\n\n\treturn nil\n}\n\n// DownloadFile downloads a single file from Azure Blob Storage\nfunc DownloadFile(ctx context.Context, containerClient *container.Client, blobName, path string) error {\n\tblobClient := containerClient.NewBlobClient(blobName)\n\n\terr := os.MkdirAll(filepath.Dir(path), 0755)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create dir for file %s: %w\", path, err)\n\t}\n\toutFile, err := os.Create(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create file %s: %w\", path, err)\n\t}\n\tdefer func() {","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/azure/azure.go#L147-L183","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the nested wrapped error: if 'unable to list blob' fix list permissions; if 'unable to download file' fix that specific blob's access.","Re-upload the directory artifact — the source may be missing or incomplete.","Grant Storage Blob Data Reader on the whole container to the credential.","Retry on transient network errors; consider narrowing the artifact to fewer blobs if large listings time out.","Verify all child blob names are valid local paths (no characters illegal on the container filesystem)."],"exampleFix":"// before: artifact re-uploaded partially, one blob 403s\nargs: [download, dir/]\n// after: re-save the directory artifact cleanly\nargo submit --output-artifact 'dir => azure blob=dir/'","handlingStrategy":"retry","validationCode":"// verify every child is downloadable before bulk download\nfiles, err := driver.ListObjects(ctx, artifact)\nif err != nil { return err }\nfor _, f := range files {\n\tif _, err := containerClient.NewBlobClient(f).GetProperties(ctx, nil); err != nil { return err }\n}","typeGuard":"func isTransientAzureErr(err error) bool {\n\tvar respErr *azcore.ResponseError\n\tif errors.As(err, &respErr) {\n\t\tswitch respErr.StatusCode {\n\t\tcase 500, 502, 503, 504:\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","tryCatchPattern":"err := driver.Load(ctx, artifact, path)\nif err != nil && isTransientAzureErr(err) {\n\t// retry with backoff; SDK already retries per-request, add loop-level retry\n}","preventionTips":["Snapshot directory artifacts (versioned prefixes) so children are immutable during download","Grant container-wide Storage Blob Data Reader instead of per-blob policies","Retry workflow runs on transient wrapped errors","Keep directory artifacts modest in size/child count"],"tags":["azure","blob-storage","directory","download"],"backgroundTag":"azure-blob-directory-download-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"}