## TL;DR

Another process holds the state lock, or a crashed process left a stale one in DynamoDB. If a run is genuinely active, wait. If the lock is stale, `terraform force-unlock [ID]` with the ID from the error; if even that fails, delete the lock row from DynamoDB directly.

## The error

```text
Error: Error locking state: Error acquiring the state lock:
ConditionalCheckFailedException: The conditional request failed
Lock Info:
  ID: terraform-s3-bucket/path/to/terraform.tfstate-md5
  Path: terraform-s3-bucket/path/to/terraform.tfstate
  Operation: OperationTypePlan
  Who: root@runner-123-concurrent-0
```

## Steps to fix

1. Check whether the holder is still alive. The `Who` and `Created` fields name the machine and time. If a CI job or teammate is mid-run, wait for it.
   - Expected: the other run finishes and your retry succeeds.
2. If the holder is gone (crashed runner, killed process), force-unlock with the exact ID from the error:
   ```bash
   terraform force-unlock terraform-s3-bucket/path/to/terraform.tfstate-md5
   ```
   - Expected: `Terraform state has been successfully unlocked!`
3. If force-unlock fails (malformed lock row, CLI cannot reach the backend), delete the row directly:
   ```bash
   aws dynamodb delete-item --table-name terraform-state-lock \
     --key '{"LockID": {"S": "your-bucket/path/terraform.tfstate"}}' \
     --region us-east-1
   ```
   - Expected: the scan of the table no longer shows the lock.
4. Run `terraform plan` to confirm the state is healthy before any apply.
   - Expected: plan runs without lock errors.

## When to use this

- Any Terraform command fails with `Error locking state ... ConditionalCheckFailedException` on an S3+DynamoDB backend, especially after a cancelled CI job or crashed laptop.

## When NOT to use this

- `Error acquiring the state lock` on a *local* backend is a lock file on disk, not DynamoDB. Never force-unlock while another operation is genuinely running: you can corrupt state.

## Compatibility

- Terraform 0.9+ through 1.x with the S3 backend `dynamodb_table` option. Terraform 1.10+ offers S3-native locking via `use_lockfile = true` as an alternative.

## Root cause

DynamoDB implements the lock as a conditional write: acquiring the lock succeeds only if no lock row exists. `ConditionalCheckFailedException` means the row already exists, i.e. someone (or something crashed mid-run) holds it. Crashed processes never run their unlock, so the row goes stale.

## Edge cases

- Two CI jobs racing on the same workspace produce this constantly; serialize them or split state per environment.
- `terraform force-unlock` refuses without the exact lock ID; copy it from the error, not from memory.
- As a last resort on the machine that held the lock: kill Terraform processes and remove local `.terraform.tfstate.lock.info` files, then verify with `ps`.