{"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":"warning","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/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/client.go#L653-L689","documentation":"Thrown by deleteMD5 when DynamoDB DeleteItem on the digest row (LockID = lockPath()+stateIDSuffix) fails. deleteMD5 is called when a state is destroyed/removed to clean up the stored MD5 digest; failure leaves a stale digest that may trigger false stale-state warnings later.","triggerScenarios":"A state-destroy or workspace-deletion path calls deleteMD5 -> c.dynClient.DeleteItem returns an error: AccessDenied, table deleted mid-operation, throttling, or item-not-found under a conditional expression.","commonSituations":"IAM principal has GetItem/PutItem but not DeleteItem; table removed before cleanup; heavy concurrency hitting provisioned write limits; SCP denying deletes.","solutions":["Grant dynamodb:DeleteItem on the table ARN.","Verify the table still exists and the backend points to it.","Scale write capacity if throttled.","Manually delete the orphaned digest row if cleanup keeps failing."],"exampleFix":"// before: missing DeleteItem\n// after\n{\n  \"Effect\": \"Allow\",\n  \"Action\": \"dynamodb:DeleteItem\",\n  \"Resource\": \"arn:aws:dynamodb:*:*:table/terraform-locks\"\n}","handlingStrategy":"try-catch","validationCode":"// Confirm DeleteItem is granted on the digest row\nfunc canDeleteDigest(ctx context.Context, ddbs *dynamodb.Client, table, digestKey string) error {\n  // probe with a throwaway row, not the real one\n  probe := digestKey + \"-probe\"\n  if _, err := ddbs.PutItem(ctx, &dynamodb.PutItemInput{TableName: &table, Item: map[string]types.AttributeValue{\"LockID\": &types.AttributeValueMemberS{Value: probe}}}); err != nil { return err }\n  _, err := ddbs.DeleteItem(ctx, &dynamodb.DeleteItemInput{TableName: &table, Key: map[string]types.AttributeValue{\"LockID\": &types.AttributeValueMemberS{Value: probe}}})\n  return err\n}","typeGuard":null,"tryCatchPattern":"if err := client.deleteMD5(ctx); err != nil {\n    // stale-digest cleanup failure is non-fatal; log and continue\n    log.Printf(\"warn: digest cleanup failed (table may need manual cleanup): %v\", err)\n}","preventionTips":["Grant dynamodb:DeleteItem on the lock table.","Treat deleteMD5 failure as warning, not fatal — the putMD5 still keeps state fresh.","Periodically reap orphaned digest rows.","Keep the table in sync with the backend config."],"tags":["dynamodb","terraform-state","iam","aws","cleanup"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}