hashicorp/terraform · error
Lock ID should be numerical value, got '%s'
Error message
Lock ID should be numerical value, got '%s'
What it means
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.
Source
Thrown at internal/backend/remote-state/gcs/client.go:131
}
return w.Close()
}()
if err != nil {
return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err))
}
info.ID = strconv.FormatInt(w.Attrs().Generation, 10)
return info.ID, nil
}
func (c *remoteClient) Unlock(id string) error {
ctx := context.TODO()
gen, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return fmt.Errorf("Lock ID should be numerical value, got '%s'", id)
}
if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(ctx); err != nil {
return c.lockError(err)
}
return nil
}
func (c *remoteClient) lockError(err error) *statemgr.LockError {
lockErr := &statemgr.LockError{
Err: err,
}
info, infoErr := c.lockInfo()
if infoErr != nil {
lockErr.Err = errors.Join(lockErr.Err, infoErr)
} else {View on GitHub (pinned to c9def3e214)
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.
Example fix
# before (wrong shape) terraform force-unlock 9f2c8d20-... # uuid from another backend # after gsutil stat gs://bucket/prefix/default.tflock | grep Generation terraform force-unlock 1681234567890123
Defensive patterns
Strategy: validation
Validate before calling
import "strconv"
func validateGCSLockID(id string) error {
if _, err := strconv.ParseInt(id, 10, 64); err != nil {
return fmt.Errorf("GCS lock ID must be the .tflock generation number, got %q", id)
}
return nil
} Type guard
func isNumericLockID(id string) bool {
_, err := strconv.ParseInt(id, 10, 64)
return err == nil
} Prevention
- 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'.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- %v Additionally, unlocking the state file on Google Clou
- writing %q failed: %v
- %q is not a valid state name
- can't set both encryption_key and kms_encryption_key
- the string provided in credentials is neither valid json nor
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/80cb7c5dace4d8b3.
Report an issue: GitHub.