{"record":{"id":"6e9067f08c0c288a","repo":"hashicorp/terraform","slug":"unable-to-delete-item-from-dynamodb-table-q-w","errorCode":null,"errorMessage":"Unable to delete item from DynamoDB table %q: %w","messagePattern":"Unable to delete item from DynamoDB table %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":671,"sourceCode":"\treturn nil\n}\n\n// remove the hash value for a deleted state\nfunc (c *RemoteClient) deleteMD5(ctx context.Context) error {\n\tif c.ddbTable == \"\" {\n\t\treturn nil\n\t}\n\n\tparams := &dynamodb.DeleteItemInput{\n\t\tKey: map[string]dynamodbtypes.AttributeValue{\n\t\t\t\"LockID\": &dynamodbtypes.AttributeValueMemberS{\n\t\t\t\tValue: c.lockPath() + stateIDSuffix,\n\t\t\t},\n\t\t},\n\t\tTableName: aws.String(c.ddbTable),\n\t}\n\tif _, err := c.dynClient.DeleteItem(ctx, params); err != nil {\n\t\treturn fmt.Errorf(\"Unable to delete item from DynamoDB table %q: %w\", c.ddbTable, err)\n\t}\n\treturn nil\n}\n\n// getLockInfoWithFile retrieves and parses a lock file from an S3 bucket.\nfunc (c *RemoteClient) getLockInfoWithFile(ctx context.Context) (*statemgr.LockInfo, error) {\n\t// Attempt to retrieve the lock file from S3.\n\tgetOutput, err := c.s3Client.GetObject(ctx, &s3.GetObjectInput{\n\t\tBucket: aws.String(c.bucketName),\n\t\tKey:    aws.String(c.lockFilePath),\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to retrieve file from S3 bucket '%s' with key '%s': %w\", c.bucketName, c.lockFilePath, err)\n\t}\n\tdefer func() {\n\t\tif cerr := getOutput.Body.Close(); cerr != nil {\n\t\t\tlog.Printf(\"failed to close S3 object body: %v\", cerr)\n\t\t}","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L653-L689","documentation":"Thrown by deleteMD5 when the DynamoDB DeleteItem that removes the state MD5 digest fails. Called from the backend's Delete path (after the S3 state object is deleted) to clean up the corresponding digest row. Failure here leaves an orphaned digest row pointing at a deleted state object.","triggerScenarios":"c.dynClient.DeleteItem at client.go:670 returns an error. Triggers: dynamodb_table deleted between the S3 delete and this call, IAM principal lacks dynamodb:DeleteItem, table throttled on write capacity, or a resource policy denying the principal.","commonSituations":"Table dropped mid-cleanup, IAM permissions narrowed after the S3 delete succeeded, write-capacity throttling during bulk workspace deletion, or cross-account role missing DDB write perms.","solutions":["Manually delete the orphaned digest row: `aws dynamodb delete-item --table-name <table> --key '{\"LockID\":{\"S\":\"<bucket>/<path>-md5\"}}'` (the stateIDSuffix is typically `-md5`).","Verify dynamodb:DeleteItem permission on the table for the principal.","Confirm the table still exists in-region; if recreated, update backend `dynamodb_table`.","For write throttling, switch the table to on-demand or raise write capacity.","Orphaned digest rows are low-risk but can cause stale-state false positives later; clear them to be safe."],"exampleFix":"# remove the orphaned digest row after a failed deleteMD5\naws dynamodb delete-item \\\n  --table-name terraform-locks \\\n  --key '{\"LockID\":{\"S\":\"tf-state-prod/prod/terraform.tfstate-md5\"}}'","handlingStrategy":"retry","validationCode":"// Before deleting state, confirm DDB delete access.\nfunc ddbDeleteReachable(ctx context.Context, c *dynamodb.Client, table string) error {\n  if _, err := c.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {\n    return err\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":"// Retry transient DeleteItem; surface ResourceNotFound/AccessDenied distinctly.\nfor i := 0; i < 3; i++ {\n  if _, err := c.dynClient.DeleteItem(ctx, params); err == nil { return nil }\n  else {\n    var apiErr smithy.APIError\n    if errors.As(err, &apiErr) && (apiErr.ErrorCode() == \"ResourceNotFoundException\" || apiErr.ErrorCode() == \"AccessDenied\") {\n      return fmt.Errorf(\"Unable to delete item from DynamoDB table %q: %w\", c.ddbTable, err)\n    }\n    time.Sleep(backoff(i))\n  }\n}","preventionTips":["Grant dynamodb:DeleteItem on the lock table in the apply role.","Clean up orphaned digest rows (LockID ending in stateIDSuffix) when manually removing state.","Use on-demand billing to avoid write throttling during workspace deletion.","Keep the table name stable across the state lifecycle."],"tags":["dynamodb","remote-state","md5","delete","iam","cleanup"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}