argoproj/argo-workflows · error

unable to upload directory %s to Azure: %w

Error message

unable to upload directory %s to Azure: %w

What it means

Save uploads a whole directory via PutDirectory, which walks the tree and calls PutFile per entry. Any per-file upload failure (auth, throttling, network, blob-name issues) surfaces as this wrapped error.

Source

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

	logger.WithField("endpoint", outputArtifact.Azure.Endpoint).
		WithField("container", outputArtifact.Azure.Container).
		WithField("blob", outputArtifact.Azure.Blob).
		Info(ctx, "Saving to Azure Blob Storage")

	isDir, err := file.IsDirectory(path)
	if err != nil {
		return fmt.Errorf("failed to test if %s is a directory: %w", path, err)
	}

	containerClient, err := azblobDriver.newAzureContainerClient(ctx)
	if err != nil {
		return fmt.Errorf("unable to create Azure Blob Container client for %s: %w", outputArtifact.Azure.Blob, err)
	}

	if isDir {
		err := PutDirectory(ctx, containerClient, outputArtifact.Azure.Blob, path)
		if err != nil {
			return fmt.Errorf("unable to upload directory %s to Azure: %w", path, err)
		}
	} else {
		err := PutFile(ctx, containerClient, outputArtifact.Azure.Blob, path)
		if err != nil {
			return fmt.Errorf("unable to upload file %s to Azure: %w", path, err)
		}
	}

	return nil
}

// SaveStream saves an artifact from an io.Reader to Azure Blob Storage
func (azblobDriver *ArtifactDriver) SaveStream(ctx context.Context, reader io.Reader, outputArtifact *wfv1.Artifact) error {
	logger := logging.RequireLoggerFromContext(ctx)
	logger.WithField("endpoint", outputArtifact.Azure.Endpoint).
		WithField("container", outputArtifact.Azure.Container).
		WithField("blob", outputArtifact.Azure.Blob).
		Info(ctx, "Streaming to Azure Blob Storage")

View on GitHub (pinned to 35bff19146)

Solutions

  1. Look at the inner PutFile error for the specific file and Azure error code.
  2. Retry the workflow if the failure was transient (429/503/timeout).
  3. Reduce artifact size/count or increase timeouts; avoid saving huge trees as artifacts.
  4. Ensure account key permissions allow blob writes (Storage Blob Data Contributor).
  5. Sanitize unusual characters in file names before they become blob keys.

Example fix

// before: saving a node_modules-style huge dir as artifact
artifacts: [{name: workdir, path: /work}]
// after: exclude volatile dirs or archive first
artifacts: [{name: workdir, path: /work/archive.tgz}]
Defensive patterns

Strategy: retry

Validate before calling

count := 0
filepath.WalkDir(path, func(p string, d fs.DirEntry, err error) error {
  count++
  return nil
})
if count > 10000 {
  return fmt.Errorf("refusing to save %d files as one artifact", count)
}

Type guard

func isThrottled(err error) bool {
  var re *azcore.ResponseError
  return errors.As(err, &re) && re.StatusCode == 429
}

Try / catch

err := driver.Save(ctx, dir, artifact)
if err != nil && strings.Contains(err.Error(), "unable to upload directory") {
  if isThrottled(errors.Unwrap(err)) {
    // backoff and retry
  }
  return err
}

Prevention

When it happens

Trigger: Saving a directory artifact when one of the recursive PutFile uploads fails: Azure authorization errors, upload stream failures, invalid blob names derived from odd file paths, request timeouts on large trees.

Common situations: Uploading directories with thousands of files and hitting throttling; file names with characters Azure rejects; intermittent network drops in-cluster; key rotation mid-upload.

Related errors


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