hashicorp/terraform · error
Unable to list objects in S3 bucket %q with prefix %q: %w
Error message
Unable to list objects in S3 bucket %q with prefix %q: %w
What it means
Thrown in Backend.Workspaces (s3/backend_state.go:78) for a ListObjectsV2 failure that is neither NoSuchBucket nor the legacy defaultWorkspaceKeyPrefix ("env:") AccessDenied special case. The underlying AWS SDK error is wrapped with %w so its code (e.g. AccessDenied, SlowDown) is reachable programmatically.
Source
Thrown at internal/backend/remote-state/s3/backend_state.go:78
}
wss := []string{backend.DefaultStateName}
ctx, baselog := baselogging.NewHcLogger(ctx, log)
ctx = baselogging.RegisterLogger(ctx, baselog)
pages := s3.NewListObjectsV2Paginator(b.s3Client, params)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)
if err != nil {
if IsA[*s3types.NoSuchBucket](err) {
return nil, diags.Append(fmt.Errorf(errS3NoSuchBucket, b.bucketName, err))
}
if foo, ok := As[smithy.APIError](err); b.workspaceKeyPrefix == defaultWorkspaceKeyPrefix && ok && foo.ErrorCode() == "AccessDenied" {
log.Warn("Unable to list non-default workspaces", "err", err.Error())
return wss[:1], nil
}
return nil, diags.Append(fmt.Errorf("Unable to list objects in S3 bucket %q with prefix %q: %w", b.bucketName, prefix, err))
}
for _, obj := range page.Contents {
ws := b.keyEnv(aws.ToString(obj.Key))
if ws != "" {
wss = append(wss, ws)
}
}
}
sort.Strings(wss[1:])
return wss, diags
}
func (b *Backend) keyEnv(key string) string {
prefix := b.workspaceKeyPrefix
if prefix == "" {View on GitHub (pinned to c9def3e214)
Solutions
- Grant s3:ListBucket on arn:aws:s3:::<bucket> scoped to the workspace prefix resource.
- If using the legacy env: prefix, ensure the policy allows listing that prefix (the code only silently downgrades AccessDenied for env:).
- Retry on SlowDown/throttling with backoff.
- Refresh expired STS credentials and re-run.
Example fix
// before: IAM only allows s3:GetObject/PutObject
// after: also allow listing the workspace prefix
// {
// "Effect": "Allow",
// "Action": ["s3:ListBucket"],
// "Resource": "arn:aws:s3:::mycorp-tfstate",
// "Condition": {"StringLike": {"s3:prefix": ["env:/*", ""]}}
// } Defensive patterns
Strategy: validation
Validate before calling
// Pre-check list permission with an explicit ListObjectsV2 call
// _, err := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
// Bucket: aws.String(bucket), Prefix: aws.String(prefix), MaxKeys: aws.Int32(1),
// })
// if err != nil { /* surface IAM/policy gap before init */ } Type guard
// Narrow the wrapped smithy API error to react by code
// var apiErr smithy.APIError
// if errors.As(err, &apiErr) {
// switch apiErr.ErrorCode() {
// case "AccessDenied": /* IAM policy */
// case "SlowDown": /* retry */
// }
// } Try / catch
// Retry throttling; surface permission errors distinctly
// var apiErr smithy.APIError
// if errors.As(err, &apiErr) && apiErr.ErrorCode()=="SlowDown" { /* backoff retry */ } else { return err } Prevention
- Scope s3:ListBucket to the workspace prefix your automation actually uses.
- If you rely on the legacy env: prefix, keep it in the policy.
- Refresh STS credentials with margin before long runs.
When it happens
Trigger: IAM principal lacks s3:ListBucket on the bucket/prefix (when using a non-legacy workspaceKeyPrefix); S3 throttling (SlowDown); STS/credentials expired mid-call; network error; a bucket policy explicitly denying ListBucket.
Common situations: Least-privilege IAM role scoped to a custom prefix but Terraform lists the whole bucket; switched workspaceKeyPrefix without updating the policy; throttled by a burst of automation; expired assumed-role credentials.
Related errors
- Unable to access object %q in S3 bucket %q: %w
- failed to upload state: %w
- failed to unlock both S3 and DynamoDB: S3 error: %v, DynamoD
- failed to unlock S3: %v
- unable to retrieve file from S3 bucket '%s' with key '%s': %
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/5da3802fa9f74593.
Report an issue: GitHub.