{"record":{"id":"35d4fd66e3640cb7","repo":"hashicorp/terraform","slug":"failed-to-retrieve-lock-info-s-35d4fd","errorCode":null,"errorMessage":"failed to retrieve lock info: %s","messagePattern":"failed to retrieve lock info: (.+?)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oss/client.go","lineNumber":373,"sourceCode":"\t\tinfoData = v[0].Value.(string)\n\t}\n\tlockInfo := &statemgr.LockInfo{}\n\terr = json.Unmarshal([]byte(infoData), lockInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn lockInfo, nil\n}\nfunc (c *RemoteClient) Unlock(id string) error {\n\tif c.otsTable == \"\" {\n\t\treturn nil\n\t}\n\n\tlockErr := &statemgr.LockError{}\n\n\tlockInfo, err := c.getLockInfo()\n\tif err != nil {\n\t\tlockErr.Err = fmt.Errorf(\"failed to retrieve lock info: %s\", err)\n\t\treturn lockErr\n\t}\n\tlockErr.Info = lockInfo\n\n\tif lockInfo.ID != id {\n\t\tlockErr.Err = fmt.Errorf(\"lock id %q does not match existing lock\", id)\n\t\treturn lockErr\n\t}\n\tparams := &tablestore.DeleteRowRequest{\n\t\tDeleteRowChange: &tablestore.DeleteRowChange{\n\t\t\tTableName: c.otsTable,\n\t\t\tPrimaryKey: &tablestore.PrimaryKey{\n\t\t\t\tPrimaryKeys: []*tablestore.PrimaryKeyColumn{\n\t\t\t\t\t{\n\t\t\t\t\t\tColumnName: pkName,\n\t\t\t\t\t\tValue:      c.lockPath(),\n\t\t\t\t\t},\n\t\t\t\t},","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/oss/client.go#L355-L391","documentation":"In RemoteClient.Unlock() (client.go:371-373), c.getLockInfo() failed before the delete. getLockInfo does a GetRow on the OTS lock row and json.Unmarshal's the Info column; failure here means the lock row cannot be read. Unlock returns a statemgr.LockError with this as Err and nil Info.","triggerScenarios":"GetRow fails (tablestore:GetRow denied, table missing, OTS error) OR the Info column JSON fails to unmarshal into statemgr.LockInfo (corrupted/edited row). Either way the unlock cannot proceed because the code must first verify the lock ID matches (line 378).","commonSituations":"Lock row Info column manually edited or written by a non-terraform tool; GetRow permission missing; OTS table dropped; the lock row's Info value is empty/non-JSON.","solutions":["Grant tablestore:GetRow on the table.","Inspect the OTS row; if its Info JSON is corrupt, force-unlock by deleting the row directly.","Ensure no external tool writes to the lock table's Info column.","Recreate the OTS table if it was removed."],"exampleFix":"# before: cannot read lock row\nterraform force-unlock <id>   # also fails on getLockInfo\n\n# fix: delete the OTS row directly in the console (LockID = <bucket>/<stateFile>),\n# then re-run terraform init.","handlingStrategy":"fallback","validationCode":"// Validate that the lock row's Info column unmarshals before attempting Unlock.\nfunc lockInfoReadable(c *tablestore.TableStoreClient, table, path string) (*statemgr.LockInfo, error) {\n    // reuse getLockInfo logic; return error if Info missing/corrupt\n    return info, nil\n}","typeGuard":null,"tryCatchPattern":"// If getLockInfo fails, fall back to direct OTS row deletion (force-unlock by hand).\nif strings.Contains(err.Error(), \"failed to retrieve lock info\") {\n    deleteOTSLockRowManually(table, path)\n}","preventionTips":["Grant tablestore:GetRow on the lock table.","Never hand-edit the Info JSON column.","Keep the OTS table present for the lifetime of the backend."],"tags":["alibaba-cloud","tablestore","ots","remote-state","locking","unlock","permissions","go"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}