argoproj/argo-workflows · error

unable to create local directory %s: %w

Error message

unable to create local directory %s: %w

What it means

After successfully listing the directory's blobs, DownloadDirectory creates the local destination directory with os.MkdirAll(path, 0755) before writing children. This error means creating that local directory failed — a purely local filesystem problem.

Source

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

	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 {
			return fmt.Errorf("unable to download file %s: %w", localPath, err)
		}
	}
	return nil
}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Ensure the artifact `path` is a clean writable directory location (does not exist as a file).
  2. Fix volume permissions so the executor user can create the directory (securityContext fsGroup/runAsUser).
  3. Check the wrapped syscall: EACCES => permissions, EROFS => read-only mount, ENAMETOOLONG => shorter prefix.
  4. Pre-create the target directory in the image or a prior step.
  5. Point directory artifacts at an emptyDir or dedicated PVC path.

Example fix

// before: path exists as file
path: /mnt/data    # regular file
// after
path: /mnt/data/dir
Defensive patterns

Strategy: validation

Validate before calling

st, err := os.Lstat(path)
if err == nil && !st.IsDir() {
	return fmt.Errorf("%s exists as a file; choose a directory path", path)
}
if err := os.MkdirAll(path, 0o755); err != nil { return err }

Type guard

func canCreateDir(p string) error {
	if fi, err := os.Lstat(p); err == nil && !fi.IsDir() {
		return fmt.Errorf("file blocks dir %s", p)
	}
	return nil
}

Prevention

When it happens

Trigger: os.MkdirAll(path) fails: an existing regular file occupies `path` (ENOTDIR/EEXIST semantics via intermediate), read-only volume, permission denied for the executor user, or ENOSPC/ENAMETOOLONG.

Common situations: Directory artifact path already exists as a file in the container, artifact path is on a read-only configMap/config mount, non-root executor cannot write to the given volume, deep blob prefixes making the local path too long.

Related errors


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