{"record":{"id":"a7125eb95ec26bf6","repo":"hashicorp/terraform","slug":"failed-to-upload-state-to-v-v","errorCode":null,"errorMessage":"Failed to upload state to %v: %v","messagePattern":"Failed to upload state to (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/gcs/client.go","lineNumber":77,"sourceCode":"\n\treturn result, diags\n}\n\nfunc (c *remoteClient) Put(data []byte) tfdiags.Diagnostics {\n\tvar diags tfdiags.Diagnostics\n\tctx := context.TODO()\n\terr := func() error {\n\t\tstateFileWriter := c.stateFile().NewWriter(ctx)\n\t\tif len(c.kmsKeyName) > 0 {\n\t\t\tstateFileWriter.KMSKeyName = c.kmsKeyName\n\t\t}\n\t\tif _, err := stateFileWriter.Write(data); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn stateFileWriter.Close()\n\t}()\n\tif err != nil {\n\t\treturn diags.Append(fmt.Errorf(\"Failed to upload state to %v: %v\", c.stateFileURL(), err))\n\t}\n\n\treturn diags\n}\n\nfunc (c *remoteClient) Delete() tfdiags.Diagnostics {\n\tvar diags tfdiags.Diagnostics\n\n\tctx := context.TODO()\n\tif err := c.stateFile().Delete(ctx); err != nil {\n\t\treturn diags.Append(fmt.Errorf(\"Failed to delete state file %v: %v\", c.stateFileURL(), err))\n\t}\n\n\treturn diags\n}\n\n// Lock writes to a lock file, ensuring file creation. Returns the generation\n// number, which must be passed to Unlock().","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/gcs/client.go#L59-L95","documentation":"remoteClient.Put() opens a writer, writes data, and closes; any failure in Write or Close is wrapped here with the gs:// destination URL. Close is where GCS commits the upload, so most real failures (auth expired mid-flight, quota, precondition) surface on Close.","triggerScenarios":"'terraform apply' / 'state push' persisting state: the access token expired between plan and apply; CSEK provided for read but not write; bucket is full (quota), in a region under maintenance; KMS key disabled; precondition (ifGenerationMatch) failed due to concurrent writer.","commonSituations":"Long-running apply where the OAuth token lifetime (<1h) elapses; SA lacks storage.objects.create; kms_encryption_key references a disabled/destroyed key; bucket retention policy conflict.","solutions":["Inspect %v: 401 → refresh credentials; 403 → IAM/quota; 429 → throttle/retry; KMS error → enable key.","Grant SA 'roles/storage.objectAdmin' and (if KMS) 'roles/cloudkms.cryptoKeyEncrypter' on the key.","For token-expiry, ensure ADC/workload-identity refreshes automatically and avoid hours-long single applies; split or use state push separately.","If concurrent writers, enable and rely on state locking (it uses a separate .tflock) — confirm the lock is being honored."],"exampleFix":"// before: kms key disabled\nkms_encryption_key = \"projects/p/locations/global/keyRings/kr/cryptoKeys/disabled-key\"\n\n// after\ngcloud kms keys enable disabled-key --location global --keyring kr\nterraform apply","handlingStrategy":"retry","validationCode":"// Confirm SA can write before apply\nctx := context.Background()\nw := client.Bucket(bucket).Object(prefix+\"/probe\").If(storage.Conditions{DoesNotExist: true}).NewWriter(ctx)\nif _, err := w.Write([]byte(\"probe\")); err != nil { return err }\nreturn w.Close()","typeGuard":null,"tryCatchPattern":"diags := client.Put(data)\nif diags.HasErrors() {\n    if isAuthExpired(diags) { refreshCredentials(); /* retry once */ }\n    if isQuota(diags) { return fmt.Errorf(\"GCS write quota exceeded, retry later\") }\n}","preventionTips":["Use Workload Identity / ADC so tokens refresh automatically during long applies.","Grant the SA 'roles/storage.objectAdmin'.","Keep KMS keys enabled; alert on disable."],"tags":["gcs","gcp","state","write","auth","kms","network"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T20:17:04.800Z"}