argoproj/argo-workflows · error

failed to put directory: %w

Error message

failed to put directory: %w

What it means

The upload itself failed: after bucket handling, PutDirectory (if the local path is a directory) could not upload the directory contents to the S3 key prefix. The whole artifact save fails and the retry wrapper decides whether to retry.

Source

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

	if createBucketIfNotPresent != nil {
		log.WithField("bucket", outputArtifact.S3.Bucket).Info(ctx, "creating bucket")
		makeBucketErr := s3cli.MakeBucket(outputArtifact.S3.Bucket, minio.MakeBucketOptions{
			Region:        outputArtifact.S3.Region,
			ObjectLocking: outputArtifact.S3.CreateBucketIfNotPresent.ObjectLocking,
		})
		alreadyExists := bucketAlreadyExistsErr(makeBucketErr)
		log.WithField("bucket", outputArtifact.S3.Bucket).
			WithField("alreadyExists", alreadyExists).
			WithError(makeBucketErr).
			Info(ctx, "create bucket failed")
		if makeBucketErr != nil && !alreadyExists {
			return !isTransientS3Err(ctx, makeBucketErr), fmt.Errorf("failed to create bucket %s: %w", outputArtifact.S3.Bucket, makeBucketErr)
		}
	}

	if isDir {
		if err = s3cli.PutDirectory(outputArtifact.S3.Bucket, outputArtifact.S3.Key, path); err != nil {
			return !isTransientS3Err(ctx, err), fmt.Errorf("failed to put directory: %w", err)
		}
	} else {
		if err = s3cli.PutFile(outputArtifact.S3.Bucket, outputArtifact.S3.Key, path); err != nil {
			return !isTransientS3Err(ctx, err), fmt.Errorf("failed to put file: %w", err)
		}
	}
	return true, nil
}

func bucketAlreadyExistsErr(err error) bool {
	resp := &minio.ErrorResponse{}
	// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
	alreadyExistsCodes := map[string]bool{"BucketAlreadyExists": true, "BucketAlreadyOwnedByYou": true}
	return errors.As(err, resp) && alreadyExistsCodes[resp.Code]
}

// ListObjects returns the files inside the directory represented by the Artifact
func (s3Driver *ArtifactDriver) ListObjects(ctx context.Context, artifact *wfv1.Artifact) ([]string, error) {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Retry the workflow step — transient errors are retried by the executor backoff
  2. Verify s3:PutObject (and s3:ListBucket) on the target bucket/prefix
  3. Check MinIO server disk/capacity if using MinIO
  4. Increase artifact bandwidth/reduce artifact size or split the directory
  5. Check the endpoint network path (DNS, TLS, proxies) from the workflow pod
Defensive patterns

Strategy: retry

Validate before calling

aws s3api head-bucket --bucket my-bucket && aws s3api put-object --bucket my-bucket --key .probe --body /dev/null

Try / catch

err := s3Driver.Save(ctx, path, art)
if err != nil {
    if isTransientS3Err(ctx, err) {
        // executor already retried; surface or re-run the node
    }
    return fmt.Errorf("artifact upload failed: %w", err)
}

Prevention

When it happens

Trigger: saveS3Artifact with isDir=true: s3cli.PutDirectory(bucket, key, path) failed — transient S3 error (throttle/network), access denied on PutObject, multipart upload failure on large trees, or prefix name conflicts.

Common situations: Uploading large artifact directories that hit timeouts/rate limits; IAM lacking s3:PutObject; MinIO disk full server-side; flaky network between executor and endpoint causing repeated retries that eventually give up.

Related errors


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