hashicorp/terraform · error
failed to lock OSS state: %s
Error message
failed to lock OSS state: %s
What it means
In StateMgr() (backend_state.go:149-151), when initializing a brand-new workspace state, client.Lock(lockInfo) failed. Lock() (client.go:153) attempts a conditional PutRow into TableStore; on failure it returns a statemgr.LockError whose message is wrapped here. This means another process holds the lock or the OTS write was rejected.
Source
Thrown at internal/backend/remote-state/oss/backend_state.go:151
}
log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing)
exists := false
for _, s := range existing {
if s == name {
exists = true
break
}
}
// We need to create the object so it's listed by States.
if !exists {
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
if err != nil {
return nil, diags.Append(fmt.Errorf("failed to lock OSS state: %s", err))
}
// Local helper function so we can call it multiple places
lockUnlock := func(e error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
}
return e
}
// Grab the value
if err := stateMgr.RefreshState(); err != nil {
err = lockUnlock(err)
return nil, diags.Append(err)
}
// If we have no state, we have to create an empty state
if v := stateMgr.State(); v == nil {View on GitHub (pinned to c9def3e214)
Solutions
- Run terraform force-unlock <LOCK_ID> using the lock ID from the error to clear the stale row.
- Serialize workspace initialization (one runner/branch at a time) to avoid concurrent init races.
- Confirm the credentials have tablestore:PutRow and tablestore:GetRow on the OTS table.
- If OTS is throttling, retry after a brief backoff or raise the instance throughput quota.
Example fix
# before: stale lock blocks init terraform init # after terraform force-unlock a1b2c3d4-... terraform init
Defensive patterns
Strategy: try-catch
Validate before calling
// Before init, check whether a lock row already exists for this state path.
func lockHeld(c *tablestore.TableStoreClient, table, lockPath string) bool {
_, err := c.GetRow(&tablestore.GetRowRequest{SingleRowQueryCriteria: &tablestore.SingleRowQueryCriteria{
TableName: table,
PrimaryKey: &tablestore.PrimaryKey{PrimaryKeys: []*tablestore.PrimaryKeyColumn{
{ColumnName: "LockID", Value: lockPath},
}},
MaxVersion: 1,
}})
return err == nil
} Try / catch
// On init, if the error indicates a held lock, prompt for force-unlock.
if err != nil && strings.Contains(err.Error(), "failed to lock OSS state") {
fmt.Println("state is locked; run: terraform force-unlock <ID>")
return err
} Prevention
- Serialize workspace initialization across CI runners.
- Always exit CI with terraform force-unlock on failure, or use a finally-block.
- Confirm the OTS table and PutRow permission before relying on locking.
When it happens
Trigger: StateMgr creating a not-yet-existing workspace takes an init lock; the OTS PutRow with RowExistenceExpectation_EXPECT_NOT_EXIST fails because a lock row already exists (concurrent init, stale lock from a crashed run) or because of an OTS service/permission error.
Common situations: Two CI runners initializing the same workspace simultaneously; a previous terraform process was killed leaving a stale lock row; OTS throttling; RAM policy lacks tablestore:PutRow.
Related errors
- error describing table store %s: %#v
- Error unlocking Alibaba Cloud OSS state file: Lock ID: %s E
- invoking PutRow got an error: %#v
- getting lock info got an error: %#v
- failed to retrieve lock info: %s
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/8bb340a8851a9f33.
Report an issue: GitHub.