{"record":{"id":"eb1e5f55712be8aa","repo":"argoproj/argo-workflows","slug":"failed-to-test-if-s-is-a-directory-w-eb1e5f","errorCode":null,"errorMessage":"failed to test if %s is a directory: %w","messagePattern":"failed to test if (.+?) is a directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/artifacts/s3/s3.go","lineNumber":219,"sourceCode":"\n\treturn err\n}\n\n// loadS3Artifact downloads artifacts from an S3 compliant storage\n// returns true if the download is completed or can't be retried (non-transient error)\n// returns false if it can be retried (transient error)\nfunc loadS3Artifact(ctx context.Context, s3cli Client, inputArtifact *wfv1.Artifact, path string) (bool, error) {\n\torigErr := s3cli.GetFile(inputArtifact.S3.Bucket, inputArtifact.S3.Key, path)\n\tif origErr == nil {\n\t\treturn true, nil\n\t}\n\tif !IsS3ErrCode(origErr, \"NoSuchKey\") {\n\t\treturn !isTransientS3Err(ctx, origErr), fmt.Errorf(\"failed to get file: %w\", origErr)\n\t}\n\t// If we get here, the error was a NoSuchKey. The key might be an s3 \"directory\"\n\tisDir, err := s3cli.IsDirectory(inputArtifact.S3.Bucket, inputArtifact.S3.Key)\n\tif err != nil {\n\t\treturn !isTransientS3Err(ctx, err), fmt.Errorf(\"failed to test if %s is a directory: %w\", inputArtifact.S3.Key, err)\n\t}\n\tif !isDir {\n\t\t// It's neither a file, nor a directory. Return the original NoSuchKey error\n\t\treturn true, argoerrs.New(argoerrs.CodeNotFound, origErr.Error())\n\t}\n\n\tif err = s3cli.GetDirectory(inputArtifact.S3.Bucket, inputArtifact.S3.Key, path); err != nil {\n\t\treturn !isTransientS3Err(ctx, err), fmt.Errorf(\"failed to get directory: %w\", err)\n\t}\n\treturn true, nil\n}\n\n// OpenStream opens a stream reader for an artifact from S3 compliant storage\nfunc (s3Driver *ArtifactDriver) OpenStream(ctx context.Context, inputArtifact *wfv1.Artifact) (io.ReadCloser, error) {\n\tlog := logging.RequireLoggerFromContext(ctx)\n\tlog.WithField(\"key\", inputArtifact.S3.Key).Info(ctx, \"S3 OpenStream\")\n\t//nolint:contextcheck\n\ts3cli, err := s3Driver.newClient(log.NewBackgroundContext())","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/artifacts/s3/s3.go#L201-L237","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Grant the artifact IAM role s3:ListBucket on the bucket so IsDirectory can list the key prefix","Check bucket/endpoint config (endpoint, region, insecure) in the artifact repository config and retry","If transient (throttle/network), let the executor retry — the driver already marks transient errors retryable","Verify credentials (accessKey/secretKey or workload identity) are valid and not expired"],"exampleFix":"// before: IAM policy with only GetObject\n{\"Action\": [\"s3:GetObject\"], \"Resource\": [\"arn:aws:s3:::my-bucket/*\"]}\n// after: add ListBucket on the bucket\n{\"Action\": [\"s3:GetObject\", \"s3:ListBucket\"], \"Resource\": [\"arn:aws:s3:::my-bucket\", \"arn:aws:s3:::my-bucket/*\"]}","handlingStrategy":"validation","validationCode":"// Check ListBucket access before submitting workflows that load S3 artifacts\naws s3api list-objects-v2 --bucket my-bucket --prefix my/key/ --max-items 1 || echo \"grant s3:ListBucket\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include s3:ListBucket alongside s3:GetObject in artifact IAM policies","Use workflow-level service accounts with a shared, tested S3 policy","Smoke-test artifact loading in CI against the real bucket","Prefer explicit file keys over directory prefixes to skip IsDirectory probes"],"tags":["s3","artifacts","aws","permissions"],"backgroundTag":"s3-access-denied","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}