hashicorp/terraform · error

Failed to open state file at

Error message

Failed to open state file at %v: %v

What it means

Thrown by the remote client Get when stateFile().NewReader returns an error other than storage.ErrObjectNotExist. ErrObjectNotExist is treated as 'no state yet' and is silent; any other reader-creation error (permissions, transport, transient) is wrapped here with the state file URL.

Solutions

  1. Grant roles/storage.objectViewer (or objectAdmin) for the state object path.
  2. Inspect the wrapped %v error to distinguish permission from transport failure.
  3. Verify the bucket name and prefix are correct.
  4. Retry transient network errors; the operation is read-only and idempotent.

Example fix

// before — missing get permission
// after
gsutil iam ch serviceAccount:terraform@proj.iam.gserviceaccount.com:roles/storage.objectViewer gs://tf-state
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight check: account can read the state object.
// gsutil stat gs://<bucket>/<prefix>default.tfstate

Type guard

func isErrObjectNotExist(err error) bool { return errors.Is(err, storage.ErrObjectNotExist) }

Try / catch

// Treat NotFound as 'no state', all else as error (mirror the source).
if errors.Is(err, storage.ErrObjectNotExist) {
    return nil, diags // no state yet
}
return nil, diags.Append(fmt.Errorf("Failed to open state file at %v: %v", url, err))

Prevention

When it happens

Trigger: NewReader fails with a non-NotFound error — insufficient storage.objects.get permission, network failure opening the read stream, bucket disabled, or object archived to a colder storage class requiring restoration.

Common situations: Service account has list but not get; transient 5xx opening the stream; the bucket was renamed; custom endpoint misroutes the read.

Related errors


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

Appendix: 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 d32a084675)