{"record":{"id":"d134d5c7678a5a06","repo":"hashicorp/terraform","slug":"invoking-putrow-got-an-error-v","errorCode":null,"errorMessage":"invoking PutRow got an error: %#v","messagePattern":"invoking PutRow got an error: %#v","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oss/client.go","lineNumber":195,"sourceCode":"\t\t},\n\t\tColumns: []tablestore.AttributeColumn{\n\t\t\t{\n\t\t\t\tColumnName: \"Info\",\n\t\t\t\tValue:      string(info.Marshal()),\n\t\t\t},\n\t\t},\n\t\tCondition: &tablestore.RowCondition{\n\t\t\tRowExistenceExpectation: tablestore.RowExistenceExpectation_EXPECT_NOT_EXIST,\n\t\t},\n\t}\n\n\tlog.Printf(\"[DEBUG] Recording state lock in tablestore: %#v; LOCKID:%s\", putParams, c.lockPath())\n\n\t_, err := c.otsClient.PutRow(&tablestore.PutRowRequest{\n\t\tPutRowChange: putParams,\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"invoking PutRow got an error: %#v\", err)\n\t\tlockInfo, infoErr := c.getLockInfo()\n\t\tif infoErr != nil {\n\t\t\terr = errors.Join(err, fmt.Errorf(\"\\ngetting lock info got an error: %#v\", infoErr))\n\t\t}\n\t\tlockErr := &statemgr.LockError{\n\t\t\tErr:  err,\n\t\t\tInfo: lockInfo,\n\t\t}\n\t\tlog.Printf(\"[ERROR] state lock error: %s\", lockErr.Error())\n\t\treturn \"\", lockErr\n\t}\n\n\treturn info.ID, nil\n}\n\nfunc (c *RemoteClient) getMD5() ([]byte, error) {\n\tif c.otsTable == \"\" {\n\t\treturn nil, nil","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/oss/client.go#L177-L213","documentation":"In RemoteClient.Lock() (client.go:191-195), otsClient.PutRow failed. The PutRow uses RowExistenceExpectation_EXPECT_NOT_EXIST (line 185), so the most common cause is that a lock row already exists (conditional insert rejected) - i.e. the state is already locked. %#v includes the OTS error code. Lock() then enriches by fetching existing lock info and wraps it in a statemgr.LockError.","triggerScenarios":"Conditional PutRow into the OTS table fails: OTSConditionCheckFail (row exists -> already locked), throttling, AccessDenied on tablestore:PutRow, or table missing. Because the condition is EXPECT_NOT_EXIST, a pre-existing lock row reliably produces this.","commonSituations":"Concurrent terraform apply/plan on the same workspace; a previous run crashed holding the lock; CI parallelism on one workspace; OTS table deleted; insufficient PutRow permissions.","solutions":["Check the embedded lock info (the error includes getLockInfo output) to see who holds it; coordinate or wait.","If stale, run terraform force-unlock <LOCK_ID> then retry.","Grant tablestore:PutRow; ensure the OTS table exists.","Serialize CI runs per workspace to avoid contention."],"exampleFix":"# before: two runners lock same workspace -> PutRow conditional fail\n# fix: clear stale lock\nterraform force-unlock 9f0e...\nterraform apply","handlingStrategy":"try-catch","validationCode":"// Pre-check the lock row; if present, do not even attempt Lock - surface it.\nfunc lockPresent(c *tablestore.TableStoreClient, table, path string) bool {\n    resp, err := c.GetRow(&tablestore.GetRowRequest{SingleRowQueryCriteria: &tablestore.SingleRowQueryCriteria{\n        TableName: table, PrimaryKey: pk(path), MaxVersion: 1,\n    }})\n    return err == nil && len(resp.GetColumnMap().Columns[\"Info\"]) > 0\n}","typeGuard":null,"tryCatchPattern":"// On LockError, extract the held lock info and either wait or force-unlock.\nif le, ok := err.(*statemgr.LockError); ok {\n    if le.Info != nil {\n        fmt.Printf(\"held by %s since %s; force-unlock %s\\n\", le.Info.Who, le.Info.Created, le.Info.ID)\n    }\n}","preventionTips":["One writer per workspace at a time; queue CI jobs.","Always release locks (clean exit) or force-unlock on failure.","Confirm tablestore:PutRow permission."],"tags":["alibaba-cloud","tablestore","ots","remote-state","locking","conditional-write","go"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}