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

  1. 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.
  2. Run 'terraform force-unlock <numeric-generation>' with the integer from the GCS backend.
  3. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/80cb7c5dace4d8b3. Report an issue: GitHub.