argoproj/argo-workflows · error

failed to list directory: %w

Error message

failed to list directory: %w

What it means

Wraps a failure from s3cli.ListDirectory when loading an S3 artifact directory. Called inside the retry backoff of listS3Artifact; transient errors are retried, non-transient ones fail the load. Means the S3 API call enumerating objects under the artifact key prefix failed.

Source

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

			s3cli, err := s3Driver.newClient(ctx)
			if err != nil {
				return !isTransientS3Err(ctx, err), fmt.Errorf("failed to create new S3 client: %w", err)
			}
			done, files, err = listObjects(ctx, s3cli, artifact)
			return done, err
		})

	return files, err
}

// listObjects returns the files inside the directory represented by the Artifact
// returns true if success or can't be retried (non-transient error)
// returns false if it can be retried (transient error)
func listObjects(ctx context.Context, s3cli Client, artifact *wfv1.Artifact) (bool, []string, error) {
	var files []string
	files, err := s3cli.ListDirectory(artifact.S3.Bucket, artifact.S3.Key)
	if err != nil {
		return !isTransientS3Err(ctx, err), files, fmt.Errorf("failed to list directory: %w", err)
	}
	log := logging.RequireLoggerFromContext(ctx)
	log.WithFields(logging.Fields{"bucket": artifact.S3.Bucket, "key": artifact.S3.Key, "files": files}).Debug(ctx, "successfully listing S3 directory")

	if len(files) == 0 {
		directoryExists, err := s3cli.KeyExists(artifact.S3.Bucket, artifact.S3.Key)
		if err != nil {
			return !isTransientS3Err(ctx, err), files, fmt.Errorf("failed to check if key %s exists from bucket %s: %w", artifact.S3.Key, artifact.S3.Bucket, err)
		}
		if !directoryExists {
			return true, files, argoerrs.New(argoerrs.CodeNotFound, fmt.Sprintf("no key found of name %s", artifact.S3.Key))
		}
	}
	return true, files, nil
}

func (s3Driver *ArtifactDriver) IsDirectory(ctx context.Context, artifact *wfv1.Artifact) (bool, error) {
	s3cli, err := s3Driver.newClient(ctx)

View on GitHub (pinned to 35bff19146)

Solutions

  1. Check the wrapped cause for the concrete S3 error (NoSuchBucket, AccessDenied, timeout)
  2. Verify the artifact's s3.bucket and key are correct and the bucket exists
  3. Grant the artifact credentials s3:ListBucket (and GetObject) on the bucket
  4. Confirm endpoint connectivity from the cluster/pod

Example fix

// before (IAM)
// no ListBucket permission
// after
{"Effect":"Allow","Action":["s3:ListBucket","s3:GetObject"],"Resource":["arn:aws:s3:::my-bucket","arn:aws:s3:::my-bucket/*"]}
Defensive patterns

Strategy: retry

Validate before calling

aws --endpoint-url $ENDPOINT s3api head-bucket --bucket $BUCKET
aws --endpoint-url $ENDPOINT s3api list-objects-v2 --bucket $BUCKET --prefix $KEY --max-items 1

Try / catch

// wrap load and inspect cause
files, err := ctx.LoadArtifact(artifact)
if err != nil {
  var ae smithy.APIError
  if errors.As(errors.Unwrap(err), &ae) && ae.ErrorCode() == "AccessDenied" { fixIAM() }
}

Prevention

When it happens

Trigger: listObjects calls s3cli.ListDirectory(bucket, key) and Minio returns an error: bucket does not exist, access denied to list, network error, or invalid key prefix.

Common situations: Bucket typo in artifact spec; IAM policy lacks s3:ListBucket; S3 endpoint unreachable from pod; loading an artifact whose bucket was deleted.

Related errors


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