Skip to content
Latchkey

Terraform "Error refreshing state: AccessDenied" reading the backend in CI

Before planning, Terraform reads the existing state from the backend. An S3 AccessDenied on GetObject means the runner role can reach the bucket but lacks read permission on the state key, so refresh fails before any resource is evaluated.

What this error means

plan fails early with "Error refreshing state" wrapping an S3 "AccessDenied" or "403" on GetObject for the state object path.

Terraform
Error: Error refreshing state: operation error S3: GetObject,
https response error StatusCode: 403, ... api error AccessDenied: Access Denied

Common causes

The runner role lacks s3:GetObject on the state key

The IAM policy grants the bucket but not the specific object path, so reading the state file is denied.

A bucket policy or KMS key blocks the role

A restrictive bucket policy, or a KMS key the role cannot use, denies the GetObject even when the IAM policy looks correct.

How to fix it

Grant read on the exact state path

  1. Confirm which role the runner assumes and the exact bucket/key from the error.
  2. Add s3:GetObject (and s3:ListBucket) for that prefix to the role policy.
  3. If the state is encrypted, grant kms:Decrypt on the key.
IAM policy
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:ListBucket"],
  "Resource": [
    "arn:aws:s3:::my-tf-state",
    "arn:aws:s3:::my-tf-state/env/prod/*"
  ]
}

Allow the KMS key for encrypted state

Add kms:Decrypt (and kms:GenerateDataKey for writes) on the key that encrypts the state object so refresh and apply can read and write it.

How to prevent it

  • Scope the runner policy to the exact state bucket and key prefix.
  • Include KMS decrypt/encrypt when the backend uses SSE-KMS.
  • Verify the assumed role identity in CI before debugging policies.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →