{"record":{"id":"80cb7c5dace4d8b3","repo":"hashicorp/terraform","slug":"lock-id-should-be-numerical-value-got-s","errorCode":null,"errorMessage":"Lock ID should be numerical value, got '%s'","messagePattern":"Lock ID should be numerical value, got '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/gcs/client.go","lineNumber":131,"sourceCode":"\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}\n\n\treturn nil\n}\n\nfunc (c *remoteClient) lockError(err error) *statemgr.LockError {\n\tlockErr := &statemgr.LockError{\n\t\tErr: err,\n\t}\n\n\tinfo, infoErr := c.lockInfo()\n\tif infoErr != nil {\n\t\tlockErr.Err = errors.Join(lockErr.Err, infoErr)\n\t} else {","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/gcs/client.go#L113-L149","documentation":"Unlock(id) parses the lock ID as a base-10 int64 because the GCS backend uses the .tflock object's generation number as the lock ID. If the caller passes a non-numeric string (a UUID from another backend, a stale hand-written ID, a corrupted value), strconv.ParseInt fails and this error is returned before any delete is attempted.","triggerScenarios":"Calling terraform force-unlock with an ID that isn't the numeric generation stored in .tflock (e.g. copied from a different backend's lock error, or hand-typed). Triggered in Unlock before the conditional delete.","commonSituations":"User copies the lock ID from an S3/HTTP backend lock message (UUID) and tries it on GCS; lock ID truncated/corrupted; programmatic unlock with the wrong field.","solutions":["Get the correct numeric generation: 'gsutil stat gs://<bucket>/<prefix>/<ws>.tflock' and use the 'Generation:' value, or read it from the original lock error message.","Run 'terraform force-unlock <numeric-generation>' with the integer from the GCS backend.","If unsure of the workspace/prefix, confirm with 'terraform workspace list' and the backend prefix config first."],"exampleFix":"# before (wrong shape)\nterraform force-unlock 9f2c8d20-...   # uuid from another backend\n\n# after\ngsutil stat gs://bucket/prefix/default.tflock | grep Generation\nterraform force-unlock 1681234567890123","handlingStrategy":"validation","validationCode":"import \"strconv\"\nfunc validateGCSLockID(id string) error {\n    if _, err := strconv.ParseInt(id, 10, 64); err != nil {\n        return fmt.Errorf(\"GCS lock ID must be the .tflock generation number, got %q\", id)\n    }\n    return nil\n}","typeGuard":"func isNumericLockID(id string) bool {\n    _, err := strconv.ParseInt(id, 10, 64)\n    return err == nil\n}","tryCatchPattern":null,"preventionTips":["Always pull the lock ID via 'gsutil stat' on the .tflock, not by copying from other backends.","Train operators that GCS lock IDs are integer generations, not UUIDs.","Validate the ID shape before running 'terraform force-unlock'."],"tags":["gcs","gcp","state-lock","force-unlock","validation"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}