{"record":{"id":"b898d74119f4f88e","repo":"opentofu/opentofu","slug":"no-lock-info-found-for-q-within-the-dynamodb-tab","errorCode":null,"errorMessage":"no lock info found for: %q within the DynamoDB table: %s","messagePattern":"no lock info found for: %q within the DynamoDB table: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":451,"sourceCode":"\nfunc (c *RemoteClient) getLockInfoFromDynamoDB(ctx context.Context) (*statemgr.LockInfo, error) {\n\tgetParams := &dynamodb.GetItemInput{\n\t\tKey: map[string]dtypes.AttributeValue{\n\t\t\t\"LockID\": &dtypes.AttributeValueMemberS{Value: c.lockPath()},\n\t\t},\n\t\tProjectionExpression: aws.String(\"LockID, Info\"),\n\t\tTableName:            aws.String(c.ddbTable),\n\t\tConsistentRead:       aws.Bool(true),\n\t}\n\n\tctx, _ = attachLoggerToContext(ctx)\n\tresp, err := c.dynClient.GetItem(ctx, getParams)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(resp.Item) == 0 {\n\t\treturn nil, fmt.Errorf(\"no lock info found for: %q within the DynamoDB table: %s\", c.lockPath(), c.ddbTable)\n\t}\n\n\tvar infoData string\n\tif v, ok := resp.Item[\"Info\"]; ok {\n\t\tif v, ok := v.(*dtypes.AttributeValueMemberS); ok {\n\t\t\tinfoData = v.Value\n\t\t}\n\t}\n\n\tlockInfo := &statemgr.LockInfo{}\n\terr = json.Unmarshal([]byte(infoData), lockInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn lockInfo, nil\n}\n","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/opentofu/opentofu/blob/3561785c48c1ce615e7c50261bd351f26053efa2/internal/backend/remote-state/s3/client.go#L433-L469","documentation":"Thrown by getLockInfoFromDynamoDB (client.go:451) when a consistent GetItem on the lock table succeeds but returns zero items for the lockPath key (bucketName/stateKey). It means the code tried to read a DynamoDB lock record that does not exist. Callers hit this via Unlock -> dynamoDBUnlock, and via dynamoDBLock's failure path when it re-reads the lock to report who holds it.","triggerScenarios":"GetItem with ConsistentRead=true on key LockID=<bucket>/<path> in c.ddbTable returns an empty Item: the lock was already released, a force-unlock is run twice, the row was manually deleted, or the configured table/path differs from the one that actually holds the lock. Also occurs when dynamoDBLock's PutItem fails and the code tries to fetch the conflicting lock info from an empty table.","commonSituations":"Running tofu force-unlock twice; a crashed run already cleaned up its row; someone deleted the DynamoDB item manually while the lock still exists in S3; mismatched dynamodb_table or bucket/key values between environments so the wrong table is queried.","solutions":["Check whether a lock actually exists: aws dynamodb get-item --table-name <table> --key '{\"LockID\":{\"S\":\"<bucket>/<key>\"}}' --consistent-read","If no item is returned, there is nothing to unlock — the DynamoDB side is already clean; also check the S3 .tflock object if use_lockfile is enabled","If the get-item shows a row under a different LockID, your bucket/key/table config points at the wrong table — correct the backend config instead of deleting data","For repeated unlock attempts, capture and compare the lock ID from the original lock error before unlocking"],"exampleFix":"# check the real lock row before unlocking\naws dynamodb get-item \\\n  --table-name terraform-locks \\\n  --key '{\"LockID\":{\"S\":\"tfstate/prod/terraform.tfstate\"}}' \\\n  --consistent-read\n\n# if it returns an empty item, skip force-unlock for DynamoDB;\n# only clear the S3 lockfile if present:\n# aws s3 rm s3://tfstate/prod/terraform.tfstate.tflock","handlingStrategy":"type-guard","validationCode":"// confirm a lock row exists before attempting unlock\nfunc lockRowExists(ctx context.Context, dyn *dynamodb.Client, table, lockPath string) bool {\n  out, err := dyn.GetItem(ctx, &dynamodb.GetItemInput{\n    TableName:      aws.String(table),\n    Key:            map[string]types.AttributeValue{\"LockID\": &types.AttributeValueMemberS{Value: lockPath}},\n    ConsistentRead: aws.Bool(true),\n  })\n  return err == nil && len(out.Item) > 0\n}","typeGuard":"func isNoLockInfoErr(err error) bool {\n  return err != nil && strings.Contains(err.Error(), \"no lock info found for:\")\n}","tryCatchPattern":"if err := unlocker.Unlock(ctx, id); err != nil {\n  if isNoLockInfoErr(err) {\n    // row already gone: not a real failure, verify S3 side only\n    log.Println(\"dynamodb lock already absent\")\n  } else {\n    return err\n  }\n}","preventionTips":["Capture the lock ID at acquisition time and unlock exactly once","Scripted force-unlock should check get-item first and skip if empty","Keep dynamodb_table/bucket/key identical across all environments touching the state"],"tags":["dynamodb","locking","aws","state"],"backgroundTag":null,"analyzedSha":"3561785c48c1ce615e7c50261bd351f26053efa2","analyzedAt":"2026-08-15T23:27:16.226Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}