hashicorp/terraform · error

Failed to upload state to

Error message

Failed to upload state to %v: %v

What it means

Thrown by the remote client Put when writing or closing the state object writer fails. Put opens a NewWriter, optionally sets KMSKeyName, writes the data, and closes; any error in that sequence is wrapped with the state file URL.

Solutions

  1. Grant roles/storage.objectAdmin (must include objects.create and objects.update).
  2. If using kms_encryption_key, verify the key exists and the account has roles/cloudkms.cryptoKeyEncrypterDecrypter on it.
  3. Check bucket retention policy / object versioning settings.
  4. Retry transient upload failures; inspect the wrapped %v.

Example fix

// before — read-only role
// after
gsutil iam ch serviceAccount:terraform@proj.iam.gserviceaccount.com:roles/storage.objectAdmin gs://tf-state
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: verify the account can write to the bucket.
// gsutil cp /tmp/probe gs://bucket/.writeprobe && gsutil rm gs://bucket/.writeprobe
// If kms_encryption_key set: gcloud kms keys describe ... and check encrypt/decrypt IAM.

Try / catch

// Retry transient upload failures (5xx, connection reset).
for i := 0; i < 3; i++ {
    err := put(data)
    if err == nil { break }
    if !isTransient(err) { return err }
}

Prevention

When it happens

Trigger: stateFileWriter.Write or stateFileWriter.Close returns an error — insufficient storage.objects.create permission, KMS key name invalid or inaccessible, bucket is versioning-locked, network error on upload, or bucket quota exceeded.

Common situations: Service account can read but not write; kms_encryption_key references a disabled or deleted Cloud KMS key; bucket has retention policy preventing overwrite; large state upload exceeds per-request limits; transient 5xx on upload.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/gcs/client.go:77

	return result, diags
}

func (c *remoteClient) Put(data []byte) tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics
	ctx := context.TODO()
	err := func() error {
		stateFileWriter := c.stateFile().NewWriter(ctx)
		if len(c.kmsKeyName) > 0 {
			stateFileWriter.KMSKeyName = c.kmsKeyName
		}
		if _, err := stateFileWriter.Write(data); err != nil {
			return err
		}
		return stateFileWriter.Close()
	}()
	if err != nil {
		return diags.Append(fmt.Errorf("Failed to upload state to %v: %v", c.stateFileURL(), err))
	}

	return diags
}

func (c *remoteClient) Delete() tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics

	ctx := context.TODO()
	if err := c.stateFile().Delete(ctx); err != nil {
		return diags.Append(fmt.Errorf("Failed to delete state file %v: %v", c.stateFileURL(), err))
	}

	return diags
}

// Lock writes to a lock file, ensuring file creation. Returns the generation
// number, which must be passed to Unlock().

View on GitHub (pinned to d32a084675)