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

  1. If using CSEK, restore the original encryption_key (the one the state was written with) in the backend block and retry.
  2. Check the %v: a 403 means IAM on that object; a key error means CSEK mismatch; a 5xx means retry.
  3. Grant the SA 'roles/storage.objectViewer' (or objectAdmin) on the bucket/prefix.
  4. 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

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


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