argoproj/argo-workflows · error

failed to test if %s is a directory: %w

Error message

failed to test if %s is a directory: %w

What it means

Save first checks with file.IsDirectory whether the local artifact path is a directory, to choose PutDirectory vs PutFile. If os.Stat on the path fails, the stat error is wrapped with this message. It almost always means the local file/directory that should have been saved does not exist.

Source

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

		// Not a directory (and not successful retrieval of an empty file), so return
		// the original BlobNotFound error
		return nil, fmt.Errorf("unable to open blob stream for %s: %w", artifact.Azure.Blob, origErr)
	}

	return response.Body, nil
}

// Save saves an artifact to Azure Blob Storage
func (azblobDriver *ArtifactDriver) Save(ctx context.Context, path string, 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, "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)
		}
	}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Verify the output artifact 'path' matches where the step actually writes (ls the mount to confirm).
  2. Make the step fail loudly if the file wasn't produced, or mark the artifact optional.
  3. Ensure the path is inside a mounted volume shared between containers in the pod.
  4. Check container working directory assumptions — use absolute paths.
  5. Inspect earlier step logs for a failure that prevented file creation.

Example fix

// before
outputs:
  artifacts:
    - {name: out, path: /mnt/out/results.tgz}   # file written to /work/results.tgz
// after
outputs:
  artifacts:
    - {name: out, path: /work/results.tgz}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.Stat(path); err != nil {
  return fmt.Errorf("artifact path %s missing before save: %w", path, err)
}

Type guard

func pathExists(p string) bool {
  _, err := os.Stat(p)
  return !errors.Is(err, os.ErrNotExist)
}

Try / catch

err := driver.Save(ctx, path, artifact)
if err != nil {
  var pe *fs.PathError
  if errors.As(err, &pe) && errors.Is(pe.Err, os.ErrNotExist) {
    // producer step didn't write the file
  }
  return err
}

Prevention

When it happens

Trigger: Save(path) where path was never created by the step: wrong template 'path' for the output artifact, step script wrote to a different location, or volume mount missing.

Common situations: Output artifact path typo (e.g. /mnt/out vs /mnt/outputs); script failed silently before writing the file; artifact path outside the mounted volume; optional artifact not configured as optional.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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