opentofu/opentofu · error

no lock info found for: %q within the DynamoDB table: %s

Error message

no lock info found for: %q within the DynamoDB table: %s

What it means

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.

Source

Thrown at internal/backend/remote-state/s3/client.go:451

func (c *RemoteClient) getLockInfoFromDynamoDB(ctx context.Context) (*statemgr.LockInfo, error) {
	getParams := &dynamodb.GetItemInput{
		Key: map[string]dtypes.AttributeValue{
			"LockID": &dtypes.AttributeValueMemberS{Value: c.lockPath()},
		},
		ProjectionExpression: aws.String("LockID, Info"),
		TableName:            aws.String(c.ddbTable),
		ConsistentRead:       aws.Bool(true),
	}

	ctx, _ = attachLoggerToContext(ctx)
	resp, err := c.dynClient.GetItem(ctx, getParams)
	if err != nil {
		return nil, err
	}

	if len(resp.Item) == 0 {
		return nil, fmt.Errorf("no lock info found for: %q within the DynamoDB table: %s", c.lockPath(), c.ddbTable)
	}

	var infoData string
	if v, ok := resp.Item["Info"]; ok {
		if v, ok := v.(*dtypes.AttributeValueMemberS); ok {
			infoData = v.Value
		}
	}

	lockInfo := &statemgr.LockInfo{}
	err = json.Unmarshal([]byte(infoData), lockInfo)
	if err != nil {
		return nil, err
	}

	return lockInfo, nil
}

View on GitHub (pinned to 3561785c48)

Solutions

  1. Check whether a lock actually exists: aws dynamodb get-item --table-name <table> --key '{"LockID":{"S":"<bucket>/<key>"}}' --consistent-read
  2. 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
  3. 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
  4. For repeated unlock attempts, capture and compare the lock ID from the original lock error before unlocking

Example fix

# check the real lock row before unlocking
aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID":{"S":"tfstate/prod/terraform.tfstate"}}' \
  --consistent-read

# if it returns an empty item, skip force-unlock for DynamoDB;
# only clear the S3 lockfile if present:
# aws s3 rm s3://tfstate/prod/terraform.tfstate.tflock
Defensive patterns

Strategy: type-guard

Validate before calling

// confirm a lock row exists before attempting unlock
func lockRowExists(ctx context.Context, dyn *dynamodb.Client, table, lockPath string) bool {
  out, err := dyn.GetItem(ctx, &dynamodb.GetItemInput{
    TableName:      aws.String(table),
    Key:            map[string]types.AttributeValue{"LockID": &types.AttributeValueMemberS{Value: lockPath}},
    ConsistentRead: aws.Bool(true),
  })
  return err == nil && len(out.Item) > 0
}

Type guard

func isNoLockInfoErr(err error) bool {
  return err != nil && strings.Contains(err.Error(), "no lock info found for:")
}

Try / catch

if err := unlocker.Unlock(ctx, id); err != nil {
  if isNoLockInfoErr(err) {
    // row already gone: not a real failure, verify S3 side only
    log.Println("dynamodb lock already absent")
  } else {
    return err
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/b898d74119f4f88e. Report an issue: GitHub.