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

During artifact download, if GetFile returned NoSuchKey, the driver checks whether the S3 key is actually a 'directory' (prefix). This error wraps a failure of that IsDirectory check itself — the driver could not determine the key's nature, so the download cannot proceed or be classified as not-found.

Source

Thrown at workflow/artifacts/s3/s3.go:219

	return err
}

// loadS3Artifact downloads artifacts from an S3 compliant storage
// returns true if the download is completed or can't be retried (non-transient error)
// returns false if it can be retried (transient error)
func loadS3Artifact(ctx context.Context, s3cli Client, inputArtifact *wfv1.Artifact, path string) (bool, error) {
	origErr := s3cli.GetFile(inputArtifact.S3.Bucket, inputArtifact.S3.Key, path)
	if origErr == nil {
		return true, nil
	}
	if !IsS3ErrCode(origErr, "NoSuchKey") {
		return !isTransientS3Err(ctx, origErr), fmt.Errorf("failed to get file: %w", origErr)
	}
	// If we get here, the error was a NoSuchKey. The key might be an s3 "directory"
	isDir, err := s3cli.IsDirectory(inputArtifact.S3.Bucket, inputArtifact.S3.Key)
	if err != nil {
		return !isTransientS3Err(ctx, err), fmt.Errorf("failed to test if %s is a directory: %w", inputArtifact.S3.Key, err)
	}
	if !isDir {
		// It's neither a file, nor a directory. Return the original NoSuchKey error
		return true, argoerrs.New(argoerrs.CodeNotFound, origErr.Error())
	}

	if err = s3cli.GetDirectory(inputArtifact.S3.Bucket, inputArtifact.S3.Key, path); err != nil {
		return !isTransientS3Err(ctx, err), fmt.Errorf("failed to get directory: %w", err)
	}
	return true, nil
}

// OpenStream opens a stream reader for an artifact from S3 compliant storage
func (s3Driver *ArtifactDriver) OpenStream(ctx context.Context, inputArtifact *wfv1.Artifact) (io.ReadCloser, error) {
	log := logging.RequireLoggerFromContext(ctx)
	log.WithField("key", inputArtifact.S3.Key).Info(ctx, "S3 OpenStream")
	//nolint:contextcheck
	s3cli, err := s3Driver.newClient(log.NewBackgroundContext())

View on GitHub (pinned to 35bff19146)

Solutions

  1. Grant the artifact IAM role s3:ListBucket on the bucket so IsDirectory can list the key prefix
  2. Check bucket/endpoint config (endpoint, region, insecure) in the artifact repository config and retry
  3. If transient (throttle/network), let the executor retry — the driver already marks transient errors retryable
  4. Verify credentials (accessKey/secretKey or workload identity) are valid and not expired

Example fix

// before: IAM policy with only GetObject
{"Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::my-bucket/*"]}
// after: add ListBucket on the bucket
{"Action": ["s3:GetObject", "s3:ListBucket"], "Resource": ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"]}
Defensive patterns

Strategy: validation

Validate before calling

// Check ListBucket access before submitting workflows that load S3 artifacts
aws s3api list-objects-v2 --bucket my-bucket --prefix my/key/ --max-items 1 || echo "grant s3:ListBucket"

Prevention

When it happens

Trigger: loadS3Artifact got NoSuchKey from GetFile, then s3cli.IsDirectory(bucket, key) failed — typically an S3 API error (HeadObject/ListObjects failure), auth failure on list/head permissions, throttling, or network error during the directory probe.

Common situations: IAM policy grants GetObject but denies ListBucket (IsDirectory needs list); S3-compatible endpoints (MinIO, GCS-interop) that fail ListObjectsV2 oddly; expired/stale credentials; rate limiting during heavy parallel artifact downloads.

Related errors


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