opentofu/opentofu · error · statemgr.LockError
failed to delete lock info from metadata: %w
Error message
failed to delete lock info from metadata: %w
What it means
After verifying the ID, Unlock deletes terraformlockid via SetMetadata under the lease access condition (internal/backend/remote-state/azure/client.go:265); failure is wrapped in a LockError. The lease itself is still held (it is released in the next step), so the state stays locked but the metadata remains intact — a force-unlock retry with the same ID is viable.
Source
Thrown at internal/backend/remote-state/azure/client.go:265
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
lockErr := &statemgr.LockError{}
lockInfo, err := c.getLockInfo(ctx)
if err != nil {
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %w", err)
return lockErr
}
lockErr.Info = lockInfo
if lockInfo.ID != id {
lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
return lockErr
}
c.setLeaseID(&lockInfo.ID)
if err := c.writeLockInfo(ctx, nil); err != nil {
lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %w", err)
return lockErr
}
ctx, ctxCancel := c.getContextWithTimeout(ctx)
defer ctxCancel()
leaseOptions := &lease.BlobClientOptions{
LeaseID: c.leaseID,
}
leaseClient, err := lease.NewBlobClient(c.blobClient, leaseOptions)
if err != nil {
lockErr.Err = fmt.Errorf("error getting blob lease client: %w", err)
return lockErr
}
_, err = leaseClient.ReleaseLease(ctx, nil)
if err != nil {
lockErr.Err = fmt.Errorf("error when releasing lease for azure lock: %w", err)View on GitHub (pinned to 3561785c48)
Solutions
- Retry `tofu force-unlock <same ID>` — the metadata still exists so the retry can complete
- On 403: grant write permission to the credential
- On persistent 412: break the lease manually with az CLI
- Confirm no other tooling touches the blob during unlocks
Example fix
// before # Error: failed to delete lock info from metadata: ... (state still locked) // after tofu force-unlock 1e8eca5a-... # retry; metadata intact so unlock can complete # still failing -> break the lease: az storage blob lease break --account-name st --container-name tfstate --blob-name prod.tfstate --auth-mode login
Defensive patterns
Strategy: retry
Type guard
func isLeasePrecondition(err error) bool {
var re *azcore.ResponseError
return errors.As(err, &re) && re.StatusCode == 412
} Try / catch
err := client.Unlock(ctx, id)
if le, ok := asLockError(err); ok && strings.Contains(le.Err.Error(), "failed to delete lock info") {
if isLeasePrecondition(le.Err) {
// lease changed hands: metadata intact but retry will 412 again -> break lease manually
} else {
// transient (timeout/403 fixed): retry force-unlock with the same ID
}
} Prevention
- Retry force-unlock with the same ID once the transient cause (timeout, 403) is fixed — metadata is still intact
- Ensure write permission before attempting unlock, not just read
- Prevent external lease interference while unlocks are in flight
- Escalate to manual break-lease only after a retried force-unlock fails with 412
When it happens
Trigger: 403 write denied on SetMetadata; 412 when the lease was broken or re-acquired externally between the ID check and the metadata write; CPK mismatch; context timeout.
Common situations: Credential without write permission; external lease interference mid-unlock; storage throttling.
Related errors
- blob metadata %q was empty
- failed to retrieve lock info: %w
- Error unlocking Azure state. Lock ID: %s Error: %w You may
- state blob is already locked
- error in base64 decoding lock string: %w
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/c3ece8f65d3172e3.
Report an issue: GitHub.