argoproj/argo-workflows · error

unable to remove attempted file download %s: %w

Error message

unable to remove attempted file download %s: %w

What it means

Load determined the blob key is actually a directory prefix. It previously created a local placeholder file at `path` while attempting a file download, and now must os.Remove it before re-creating the location as a directory. This error means the local filesystem refused the remove (permission, path is a non-empty dir, mount issues).

Source

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

	} else if !bloberror.HasCode(origErr, bloberror.BlobNotFound) {
		return fmt.Errorf("unable to download blob %s: %w", artifact.Azure.Blob, origErr)
	}

	isDir, err := azblobDriver.IsDirectory(ctx, artifact)
	if err != nil {
		return fmt.Errorf("unable to determine if %s is a directory: %w", artifact.Azure.Blob, err)
	}

	// It's not a directory and the file doesn't exist, Return the original NoSuchKey error.
	if !isDir && !isEmptyFile {
		return argoerrors.New(argoerrors.CodeNotFound, origErr.Error())
	}

	// When we tried to download the blob as a file, we created an empty file for the
	// blob as a target. We need to delete that empty file so we can re-create as a directory.
	err = os.Remove(path)
	if err != nil {
		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)

View on GitHub (pinned to 35bff19146)

Solutions

  1. Point the artifact path at a fresh, dedicated directory (e.g. /mnt/out) that does not already contain content.
  2. Ensure the volume backing the path is writable by the executor user (not read-only).
  3. If the path exists as a non-empty dir from a previous step, use a unique path per step or clean it before.
  4. Run the pod/executor with sufficient permissions (securityContext) to delete the placeholder file.
  5. Check the wrapped os error: EACCES => permissions, EBUSY/EXDEV => mount layout, ENOTEMPTY => existing content.

Example fix

// before: artifact path collides with existing dir
path: /mnt/src
// after: dedicated writable output dir
path: /mnt/out/artifacts
Defensive patterns

Strategy: validation

Validate before calling

st, err := os.Lstat(path)
if err == nil && st.IsDir() {
	// destination occupied by a directory; clean or pick another path
	if err := os.RemoveAll(path); err != nil { return err }
}
if err := unix.Access(filepath.Dir(path), unix.W_OK); err != nil { return err }

Type guard

func writableArtifactPath(p string) error {
	if fi, err := os.Lstat(p); err == nil && fi.IsDir() {
		return fmt.Errorf("path %s is an existing directory", p)
	}
	return nil
}

Prevention

When it happens

Trigger: os.Remove(path) fails because path is a non-empty directory (ADLS Gen2 downloaded a real directory with contents), the volume is read-only, the file is owned by another user, or path points at a protected mount point (e.g. artifact destination = a mounted volume root).

Common situations: Output/input artifact path set to an existing non-empty directory in the container, emptyDir mounted read-only, running argoexec as non-root while a prior step created the file as root.

Related errors


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