argoproj/argo-workflows · error

unable to create dir for file %s: %w

Error message

unable to create dir for file %s: %w

What it means

DownloadFile first ensures the parent directory of the destination path exists via os.MkdirAll before creating the file. This error means that MkdirAll failed, so the local filesystem could not be prepared for the blob download. It is a local FS error, not an Azure error.

Source

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

		return fmt.Errorf("unable to remove attempted file download %s: %w", path, err)
	}

	// It's a directory, so download all of the files.
	err = azblobDriver.DownloadDirectory(ctx, containerClient, artifact, path)
	if err != nil {
		return fmt.Errorf("unable to download directory %s: %w", artifact.Azure.Blob, err)
	}

	return nil
}

// DownloadFile downloads a single file from Azure Blob Storage
func DownloadFile(ctx context.Context, containerClient *container.Client, blobName, path string) error {
	blobClient := containerClient.NewBlobClient(blobName)

	err := os.MkdirAll(filepath.Dir(path), 0755)
	if err != nil {
		return fmt.Errorf("unable to create dir for file %s: %w", path, err)
	}
	outFile, err := os.Create(path)
	if err != nil {
		return fmt.Errorf("unable to create file %s: %w", path, err)
	}
	defer func() {
		if closeErr := outFile.Close(); closeErr != nil {
			logger := logging.RequireLoggerFromContext(ctx)
			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 {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Fix the artifact `path` so its parent is a writable directory and no file occupies an intermediate component.
  2. Ensure the volume is mounted at the path and is writable by the executor user.
  3. Check the wrapped syscall in the message: ENOTDIR => a file blocks a dir component; EACCES => permissions; EROFS => read-only mount.
  4. Pre-create the destination directory in the step's container image/entrypoint if needed.
  5. For directory artifacts, use a clean empty target directory.

Example fix

// before: path parent is a file
path: /tmp/out/data/result.txt  # /tmp/out/data is a regular file
// after: correct path
path: /tmp/artifacts/result.txt
Defensive patterns

Strategy: validation

Validate before calling

parent := filepath.Dir(path)
st, err := os.Stat(parent)
if err == nil && !st.IsDir() {
	return fmt.Errorf("%s exists and is not a directory", parent)
}
// verify write access
if f, err := os.CreateTemp(parent, ".probe"); err != nil { return err } else { f.Close(); os.Remove(f.Name()) }

Type guard

func ensureWritableDir(p string) error {
	if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
		return fmt.Errorf("file blocks directory %s", p)
	}
	return os.MkdirAll(p, 0o755)
}

Prevention

When it happens

Trigger: os.MkdirAll(filepath.Dir(path)) fails: parent path component is an existing regular file (ENOTDIR), read-only filesystem (EROFS), permission denied (EACCES), or path is empty/invalid on the target volume.

Common situations: Artifact path points inside a location already occupied by a file, emptyDir not mounted at the configured path, executor user lacks write access to the mount, downloading into a nested path where a file exists with the same name as an intermediate directory (common with directory artifacts).

Related errors


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