{"record":{"id":"3c0372796b66718c","repo":"hashicorp/terraform","slug":"state-blob-is-already-locked","errorCode":null,"errorMessage":"state blob is already locked","messagePattern":"state blob is already locked","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/azure/client.go","lineNumber":196,"sourceCode":"\t\tif !response.WasNotFound(properties.HttpResponse) {\n\t\t\treturn \"\", getLockInfoErr(err)\n\t\t}\n\t\t// if we don't find the blob, we need to build it\n\n\t\tcontentType := \"application/json\"\n\t\tputGOptions := blobs.PutBlockBlobInput{\n\t\t\tContentType: &contentType,\n\t\t}\n\n\t\t_, err = c.giovanniBlobClient.PutBlockBlob(ctx, c.containerName, c.keyName, putGOptions)\n\t\tif err != nil {\n\t\t\treturn \"\", getLockInfoErr(err)\n\t\t}\n\t}\n\n\t// if the blob is already locked then error\n\tif properties.LeaseStatus == blobs.Locked {\n\t\treturn \"\", getLockInfoErr(fmt.Errorf(\"state blob is already locked\"))\n\t}\n\n\tleaseID, err := c.giovanniBlobClient.AcquireLease(ctx, c.containerName, c.keyName, leaseOptions)\n\tif err != nil {\n\t\treturn \"\", getLockInfoErr(err)\n\t}\n\n\tinfo.ID = leaseID.LeaseID\n\tc.leaseID = leaseID.LeaseID\n\n\tif err := c.writeLockInfo(info); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn info.ID, nil\n}\n\nfunc (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/azure/client.go#L178-L214","documentation":"Raised in RemoteClient.Lock (client.go:194-197). Before acquiring a lease, the backend checks the blob's LeaseStatus; if it is already 'Locked', another Terraform process (or a manual lease) holds it, so Lock refuses and returns a LockError carrying the existing lock info. This is the primary concurrency-protection mechanism for Azure remote state.","triggerScenarios":"Produced at client.go:195-196 when properties.LeaseStatus == blobs.Locked at lock time, e.g. two concurrent 'terraform apply' on the same workspace, or a previous crashed run left the lease.","commonSituations":"Concurrent CI jobs on the same state file; a long-running apply blocking another; a previous Terraform crash/OOM-kill leaving the lease; a manual developer lock.","solutions":["Wait for the current Terraform process to finish, then retry","If the lock is stale, run 'terraform force-unlock <lock-id>' (the error includes the existing lock info)","Break the lease directly: az storage blob lease break --account-name <account> -c <container> -b <key>","Add CI queueing/serialization so only one run targets a workspace at a time"],"exampleFix":"# the LockError prints who holds the lock; force-unlock if stale\nterraform force-unlock <lock-id>\n\n# or break the Azure lease\naz storage blob lease break \\\n  --account-name mystage --auth-mode login \\\n  -c tfstate -b \"prod.terraform.tfstate\"","handlingStrategy":"retry","validationCode":"# Pre-check lease status before applying\nSTATUS=$(az storage blob show --account-name \"$ARM_STORAGE_ACCOUNT_NAME\" -c \"$ARM_CONTAINER_NAME\" -n \"$ARM_KEY\" --query 'properties.lease.status' -o tsv 2>/dev/null)\n[ \"$STATUS\" = \"locked\" ] && echo \"WARN: already locked -> error 154; coordinate with holder or force-unlock\" || echo \"OK: lockable\"","typeGuard":null,"tryCatchPattern":"# Wait for a lock to clear, then optionally force-unlock\nawait_lock() {\n  for attempt in $(seq 1 6); do\n    terraform apply -auto-approve && return 0\n    grep -q \"state blob is already locked\" || return 1\n    sleep 30\n  done\n  LID=$(grep -oE '[0-9a-f-]{36}' err.log | head -1)\n  echo \"Still locked after retries; review then: terraform force-unlock $LID\"\n}","preventionTips":["Queue CI jobs per workspace so concurrent applies don't contend on the lease","After a crashed run, force-unlock before the next attempt","Use 'az storage blob lease status' monitoring for shared-state pipelines"],"tags":["azure","state","locking","lease","concurrency"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}