hashicorp/terraform · error
Failed to open state file at %v: %v
Error message
Failed to open state file at %v: %v
What it means
remoteClient.Get() opens the state object via NewReader; if the error is anything other than storage.ErrObjectNotExist (which is treated as 'no state yet' and returns nil cleanly), it is surfaced here with the gs:// URL. ErrObjectNotExist does NOT produce this error.
Source
Thrown at internal/backend/remote-state/gcs/client.go:40
// blobs representing state.
// Implements "state/remote".ClientLocker
type remoteClient struct {
storageClient *storage.Client
bucketName string
stateFilePath string
lockFilePath string
encryptionKey []byte
kmsKeyName string
}
func (c *remoteClient) Get() (payload *remote.Payload, diags tfdiags.Diagnostics) {
ctx := context.TODO()
stateFileReader, err := c.stateFile().NewReader(ctx)
if err != nil {
if err == storage.ErrObjectNotExist {
return nil, diags
} else {
return nil, diags.Append(fmt.Errorf("Failed to open state file at %v: %v", c.stateFileURL(), err))
}
}
defer stateFileReader.Close()
stateFileContents, err := ioutil.ReadAll(stateFileReader)
if err != nil {
return nil, diags.Append(fmt.Errorf("Failed to read state file from %v: %v", c.stateFileURL(), err))
}
stateFileAttrs, err := c.stateFile().Attrs(ctx)
if err != nil {
return nil, diags.Append(fmt.Errorf("Failed to read state file attrs from %v: %v", c.stateFileURL(), err))
}
result := &remote.Payload{
Data: stateFileContents,
MD5: stateFileAttrs.MD5,
}View on GitHub (pinned to c9def3e214)
Solutions
- If using CSEK, restore the original encryption_key (the one the state was written with) in the backend block and retry.
- Check the %v: a 403 means IAM on that object; a key error means CSEK mismatch; a 5xx means retry.
- Grant the SA 'roles/storage.objectViewer' (or objectAdmin) on the bucket/prefix.
- Verify the object with 'gsutil cat gs://<bucket>/<prefix>/<ws>.tfstate' using the same credentials.
Example fix
// before
encryption_key = file("new-key.b64") // state was written with old-key
// after
encryption_key = file("old-key.b64") // restore the key used to write the state
terraform init && terraform state pull Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm readability with the same key before terraform
ctx := context.Background()
_, err := client.Bucket(bucket).Object(prefix+"/default.tfstate").Key(csek).NewReader(ctx)
if err != nil && err != storage.ErrObjectNotExist { log.Fatal(err) } Try / catch
payload, diags := client.Get()
if diags.HasErrors() && strings.Contains(diags.Err().Error(), "Failed to open state file") {
// likely CSEK mismatch: prompt for the original encryption_key
} Prevention
- Store the CSEK in a secret manager keyed to the same identifier as the bucket.
- Document which key wrote which state version.
- Never change encryption_key without a migration plan.
When it happens
Trigger: Reading state when the bucket exists but the object is unreadable: CSEK mismatch (the stored object was encrypted with a different key than the backend's encryption_key), permission denied on the specific object, transient read error, or a bucket-level access issue scoped to that key.
Common situations: encryption_key changed since the state was last written (most common — produces 'no decryption key was provided'); SA lacks storage.objects.get on the prefix; object archived to a colder storage with restore needed; transient GCS read error.
Related errors
- Error loading encryption key: %s
- Error decoding encryption key: %s
- Failed to delete state file %v: %v
- can't set both encryption_key and kms_encryption_key
- querying Cloud Storage failed: %v
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/5a634a5d45474ac6.
Report an issue: GitHub.