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

  1. Grant s3:ListBucket on arn:aws:s3:::<bucket> scoped to the workspace prefix resource.
  2. If using the legacy env: prefix, ensure the policy allows listing that prefix (the code only silently downgrades AccessDenied for env:).
  3. Retry on SlowDown/throttling with backoff.
  4. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/5da3802fa9f74593. Report an issue: GitHub.