hashicorp/terraform · error
invoking PutRow got an error: %#v
Error message
invoking PutRow got an error: %#v
What it means
Thrown by RemoteClient.Lock when the OTS PutRow that records the lock row fails. Because lock acquisition is conditional (RowExistenceExpectation_EXPECT_NOT_EXIST), the most common cause is a conflicting existing lock. The error is wrapped, lockInfo is best-effort fetched, and a statemgr.LockError carrying both is returned.
Solutions
- Read the lockInfo returned with the error to identify who holds the lock (use 'terraform force-unlock <ID>' if stale).
- Ensure no other run is active against the workspace.
- Verify OTS table and ots:PutRow permission.
- Retry after force-unlock or once the other run completes.
Defensive patterns
Strategy: try-catch
Try / catch
// On PutRow failure, surface the existing lock info so operators can force-unlock.
_, err := c.otsClient.PutRow(putReq)
if err != nil {
info, _ := c.getLockInfo()
return "", &statemgr.LockError{Err: err, Info: info}
} Prevention
- Serialize terraform runs per workspace.
- Always force-unlock after a crashed run.
- Confirm ots:PutRow and ots:GetRow permissions together.
When it happens
Trigger: OTS PutRow on the lock table returns an error: a row already exists for this state's lock path (someone else holds the lock), the table is missing, credentials lack ots:PutRow, or OTS is unreachable.
Common situations: Concurrent terraform runs against the same workspace; a prior crashed run left a stale lock; OTS permission/endpoint problems.
Related errors
- Error unlocking Alibaba Cloud OSS state file: Lock ID
- failed to lock OSS state
- getting lock info got an error: %#v
- failed to retrieve lock info
- error describing table store
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/d134d5c7678a5a06.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/oss/client.go:195
},
Columns: []tablestore.AttributeColumn{
{
ColumnName: "Info",
Value: string(info.Marshal()),
},
},
Condition: &tablestore.RowCondition{
RowExistenceExpectation: tablestore.RowExistenceExpectation_EXPECT_NOT_EXIST,
},
}
log.Printf("[DEBUG] Recording state lock in tablestore: %#v; LOCKID:%s", putParams, c.lockPath())
_, err := c.otsClient.PutRow(&tablestore.PutRowRequest{
PutRowChange: putParams,
})
if err != nil {
err = fmt.Errorf("invoking PutRow got an error: %#v", err)
lockInfo, infoErr := c.getLockInfo()
if infoErr != nil {
err = errors.Join(err, fmt.Errorf("\ngetting lock info got an error: %#v", infoErr))
}
lockErr := &statemgr.LockError{
Err: err,
Info: lockInfo,
}
log.Printf("[ERROR] state lock error: %s", lockErr.Error())
return "", lockErr
}
return info.ID, nil
}
func (c *RemoteClient) getMD5() ([]byte, error) {
if c.otsTable == "" {
return nil, nilView on GitHub (pinned to d32a084675)