## TL;DR
A stuck Terraform state lock is usually a crashed or killed apply, not a real concurrent run. Check who holds the lock and whether that process is still alive; if it is dead, release with terraform force-unlock using the lock ID from the error message. Never force-unlock while another apply is actually running; two writers will corrupt the state.

## Error / query
```text
Terraform "state lock" error: how to release safely
```

## Use this skill when
- terraform apply fails with "Error acquiring the state lock"
- A previous apply was killed (Ctrl-C, OOM, CI timeout) and the lock never released
- The lock has been held for hours and no apply is running
- You need the exact lock ID to pass to force-unlock

## Not for this skill when
- Another engineer or CI job is actively applying; wait for them, do not steal the lock
- The backend is unreachable (S3/DynamoDB errors); fix connectivity first, the lock is not the problem
- State is already corrupted; that is a state repair job, not a lock release

## Steps

### Step 1: Read the lock ID from the error output
```bash
terraform apply 2>&1 | grep -A2 'Lock ID' | head -5
```
Expected: a line like "Lock ID: 12345678-abcd-..."; copy it exactly, you need it for step 4.

### Step 2: Check whether the lock holder is still alive
```bash
ps aux | grep -v grep | grep terraform | head -5
```
Expected: either no terraform processes (stale lock, safe to release) or a live process (do not touch the lock).

### Step 3: Confirm no CI job is currently applying
```bash
terraform plan -lock=false -detailed-exitcode >/dev/null 2>&1; echo "plan exit: $?"
```
Expected: the plan runs fine with locking disabled; if the config itself is broken you will see it here before touching the lock.

### Step 4: Release the lock with the ID from step 1
```bash
terraform force-unlock [lock-id-from-step-1]
```
Expected: Terraform asks for confirmation, then reports the lock released; replace the bracket placeholder with the real ID.

### Step 5: Verify the lock is gone and state is readable
```bash
terraform plan -lock=false 2>&1 | tail -3
```
Expected: the plan completes with no lock error; if it shows a diff you did not expect, stop and investigate before applying.

## Variant phrasings

### "Error acquiring the state lock in CI"
The CI job was killed mid-apply; find the lock ID in the failed job's log, confirm no job is running, force-unlock from a workstation with the same backend config.

### "DynamoDB state lock stuck"
Same fix; the lock is a DynamoDB item, force-unlock deletes it. If force-unlock cannot reach DynamoDB, check IAM permissions, not the lock.

### "How long before a Terraform lock expires"
It does not expire on its own; a stale lock waits forever until someone releases it, which is why step 2 matters.

## Why it happens
Terraform writes a lock record before touching state and deletes it when done; a killed process never runs the cleanup. The lock is doing its job, it just cannot tell a dead holder from a live one.

## Edge cases and pitfalls
- Force-unlocking while a real apply runs can corrupt state; the ps check in step 2 is not optional.
- Some backends (consul, http) store the lock differently; the force-unlock command is the same, the lock ID format differs.
- If the state was mid-write when the process died, the lock may be the least of your problems; run plan and read the diff carefully.
- After releasing, find out why the apply died (OOM is the usual suspect) or it will happen again.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_gfVjQt1dhGViZ0ZLGHzQTQ
