hashicorp/terraform · error · statemgr.LockError
lock ID '%s' does not match the existing lock ID '%s'
Error message
lock ID '%s' does not match the existing lock ID '%s'
What it means
Raised in Unlock when the lock ID supplied by the caller does not equal lockInfo.ID read from the existing lock file. This is a deliberate guard preventing one process from unlocking state that another process holds. It is returned as a statemgr.LockError carrying the current LockInfo so the caller can report who actually holds the lock.
Source
Thrown at internal/backend/remote-state/oci/client.go:337
if err := json.Unmarshal(lockByteData, lockInfo); err != nil {
return lockInfo, "", fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
}
return lockInfo, *getResponse.ETag, nil
}
func (c *RemoteClient) Unlock(id string) error {
ctx := context.TODO()
logger := logWithOperation("unlock-state-file").Named(c.lockFilePath)
logger.Info("unlocking remote state")
lockInfo, etag, err := c.getLockInfo(ctx)
if err != nil {
return fmt.Errorf("Failed to retrieve lock information from OCI Object Storage: %w", err)
}
// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
if lockInfo.ID != id {
return &statemgr.LockError{
Info: lockInfo,
Err: fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID),
}
}
deleteRequest := objectstorage.DeleteObjectRequest{
NamespaceName: common.String(c.namespace),
ObjectName: common.String(c.lockFilePath),
BucketName: common.String(c.bucketName),
IfMatch: common.String(etag),
RequestMetadata: common.RequestMetadata{
RetryPolicy: getDefaultRetryPolicy(),
},
}
deleteResponse, err := c.objectStorageClient.DeleteObject(ctx, deleteRequest)
if err != nil {
return &statemgr.LockError{
Info: lockInfo,
Err: err,
}View on GitHub (pinned to c9def3e214)
Solutions
- Read the current lock file (oci os object get <lockfile>) to obtain the real lockInfo.ID and use that ID for force-unlock.
- Confirm via the LockInfo Who/Operation/Created fields that it is genuinely safe to unlock (no active run holds it).
- Only after confirming, delete the lock object directly if Terraform's unlock keeps refusing the ID.
- Avoid force-unlock when an apply is in progress; coordinate with the listed lock owner first.
Defensive patterns
Strategy: validation
Validate before calling
// Before Unlock, read the current lock file and obtain the real ID: // oci os object get --namespace <ns> --bucket-name <b> --name <lockfile> --file - // Pass the JSON's "ID" to force-unlock, not a stale/copy-pasted id.
Type guard
// The lib returns a *statemgr.LockError carrying the real holder; use it:
var le *statemgr.LockError
if errors.As(err, &le) && le.Info != nil {
// le.Info.ID is who really holds the lock; le.Info.Who/Operation/Path for context
} Try / catch
if lockInfo.ID != id {
return &statemgr.LockError{Info: lockInfo, Err: fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID)}
} Prevention
- Always force-unlock with the ID reported by Terraform, not a remembered one.
- Verify the real holder via LockInfo.Who/Operation before unlocking.
- Coordinate unlock with teammates to avoid races.
When it happens
Trigger: Unlock(id) is called with an id that differs from lockInfo.ID at client.go:334. Happens when force-unlock is attempted with a stale/wrong ID, or when the lock was re-acquired by a different run after the caller's ID became stale.
Common situations: User runs 'terraform force-unlock <old-id>' after the lock was already released and re-acquired with a new ID; copy-pasting the wrong ID; two operators racing to unlock.
Related errors
- failed to read existing lock file content: %w
- failed to unmarshal JSON data into LockInfo struct: %w
- Failed to retrieve lock information from OCI Object Storage:
- failed to access object '%s' in bucket '%s': %w
- failed to access object HttpStatusCode: %d OpcRequestId: %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/bbec0dd77fbeb0b4.
Report an issue: GitHub.