{"record":{"id":"95fde91f955a379b","repo":"hashicorp/terraform","slug":"writing-q-failed-v","errorCode":null,"errorMessage":"writing %q failed: %v","messagePattern":"writing %q failed: (.+?)","errorType":"exception","errorClass":"LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/gcs/client.go","lineNumber":118,"sourceCode":"\t// we can't set the ID until the info is written\n\tinfo.Path = c.lockFileURL()\n\n\tinfoJson, err := json.Marshal(info)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tlockFile := c.lockFile()\n\tw := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(ctx)\n\terr = func() error {\n\t\tif _, err := w.Write(infoJson); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn w.Close()\n\t}()\n\n\tif err != nil {\n\t\treturn \"\", c.lockError(fmt.Errorf(\"writing %q failed: %v\", c.lockFileURL(), err))\n\t}\n\n\tinfo.ID = strconv.FormatInt(w.Attrs().Generation, 10)\n\n\treturn info.ID, nil\n}\n\nfunc (c *remoteClient) Unlock(id string) error {\n\tctx := context.TODO()\n\n\tgen, err := strconv.ParseInt(id, 10, 64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Lock ID should be numerical value, got '%s'\", id)\n\t}\n\n\tif err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(ctx); err != nil {\n\t\treturn c.lockError(err)\n\t}","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/gcs/client.go#L100-L136","documentation":"Thrown by the remote client Lock when writing the lock file fails. Lock uses a precondition (storage.Conditions{DoesNotExist: true}) so the write is rejected if a lock file already exists; any other write/close error is also wrapped here via c.lockError.","triggerScenarios":"lockFile.If(DoesNotExist:true).NewWriter write or close fails — most commonly a 412 Precondition Failed because another process holds the lock, but also transport errors or missing objects.create permission on the lock path.","commonSituations":"Two CI jobs racing to acquire the lock; a previous terraform run crashed leaving a stale lock; service account cannot create the lock object.","solutions":["If another run legitimately holds the lock, wait for it to finish or run `terraform force-unlock <id>`.","Inspect the returned LockError.Info for the existing lock's owner and ID.","Grant objects.create permission on the bucket (lock files live alongside state).","Add a pre-run check that no stale lock exists, and clean it up deliberately."],"exampleFix":"// recovery\n$ terraform force-unlock <existing-lock-id>\n# then retry\nterraform apply","handlingStrategy":"validation","validationCode":"// Pre-flight: detect an existing lock file before attempting to acquire.\n// gsutil stat gs://bucket/<prefix>default.tflock && echo \"LOCK HELD\"","typeGuard":"func isPreconditionFailed(err error) bool {\n    var ge *googleapi.Error\n    return errors.As(err, &ge) && ge.Code == 412\n}","tryCatchPattern":"// On 412, surface existing lock info and instruct force-unlock.\nif err := c.Lock(info); err != nil {\n    if isPreconditionFailed(err) {\n        return fmt.Errorf(\"state already locked; run terraform force-unlock\")\n    }\n    return err\n}","preventionTips":["Serialize CI jobs that share state via a higher-level lock or queue.","Clean up stale locks deliberately with force-unlock, never by ignoring errors.","Ensure the account has objects.create on the lock path."],"tags":["gcs","backend","state-locking","concurrency","permissions"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}