{"record":{"id":"890d28cacf81ac7c","repo":"hashicorp/terraform","slug":"unable-to-retrieve-item-from-dynamodb-table-q-w","errorCode":null,"errorMessage":"Unable to retrieve item from DynamoDB table %q: %w","messagePattern":"Unable to retrieve item from DynamoDB table %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/client.go","lineNumber":609,"sourceCode":"func (c *RemoteClient) getMD5(ctx context.Context) ([]byte, error) {\n\tif c.ddbTable == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tgetParams := &dynamodb.GetItemInput{\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\tProjectionExpression: aws.String(\"LockID, Digest\"),\n\t\tTableName:            aws.String(c.ddbTable),\n\t\tConsistentRead:       aws.Bool(true),\n\t}\n\n\tresp, err := c.dynClient.GetItem(ctx, getParams)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to retrieve item from DynamoDB table %q: %w\", c.ddbTable, err)\n\t}\n\n\tvar val string\n\tif v, ok := resp.Item[\"Digest\"]; ok {\n\t\tif v, ok := v.(*dynamodbtypes.AttributeValueMemberS); ok {\n\t\t\tval = v.Value\n\t\t}\n\t}\n\n\tsum, err := hex.DecodeString(val)\n\tif err != nil || len(sum) != md5.Size {\n\t\treturn nil, errors.New(\"invalid md5\")\n\t}\n\n\treturn sum, nil\n}\n\n// store the hash of the state so that clients can check for stale state files.","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/client.go#L591-L627","documentation":"Thrown by getMD5 when the DynamoDB GetItem that retrieves the stored state MD5 digest fails. The digest (stored under the LockID + stateIDSuffix key, Digest attribute) lets clients detect locally-cached stale state. A GetItem error here aborts the staleness check, which usually surfaces upstream as a state-read failure.","triggerScenarios":"c.dynClient.GetItem at client.go:607 returns an error. Triggers: dynamodb_table configured but the table was deleted/renamed, IAM principal lacks dynamodb:GetItem, region/account mismatch, table throttled on read capacity, or a resource-based policy denying the principal.","commonSituations":"DynamoDB lock table recreated without updating backend config, cross-account role missing DDB read perms, switched AWS profile so the table is unreachable, or heavy concurrent reads exhausting provisioned read capacity.","solutions":["Verify the table exists and the name matches backend config: `aws dynamodb describe-table --table-name <table>` in the configured region/profile.","Confirm dynamodb:GetItem permission on arn:aws:dynamodb:<region>:<acct>:table/<table> for the principal.","If the table was recreated, update `dynamodb_table` to the new name; note the old digest rows become orphaned (harmless).","For read throttling, switch to on-demand billing or raise read capacity, then retry.","Temporarily removing `dynamodb_table` disables staleness tracking and lets state ops proceed while DDB is restored."],"exampleFix":"# before: stale/missing ddb table name\nbackend \"s3\" {\n  bucket         = \"tf-state-prod\"\n  dynamodb_table = \"terraform-locks-gone\"\n}\n# after\nbackend \"s3\" {\n  bucket         = \"tf-state-prod\"\n  dynamodb_table = \"terraform-locks\"   # existing table\n  region         = \"us-west-2\"\n}","handlingStrategy":"retry","validationCode":"// Before reading state, confirm the DDB digest table is reachable.\nfunc ddbTableReachable(ctx context.Context, c *dynamodb.Client, table string) error {\n  if _, err := c.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {\n    return fmt.Errorf(\"dynamodb table %s not reachable: %w\", table, err)\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":"// Retry transient GetItem; fail fast on ResourceNotFound/AccessDenied.\nfor i := 0; i < 3; i++ {\n  resp, err := c.dynClient.GetItem(ctx, getParams)\n  if err == nil { /* process resp */ return sum, nil }\n  var apiErr smithy.APIError\n  if errors.As(err, &apiErr) {\n    if apiErr.ErrorCode() == \"ResourceNotFoundException\" || apiErr.ErrorCode() == \"AccessDenied\" {\n      return nil, fmt.Errorf(\"Unable to retrieve item from DynamoDB table %q: %w\", c.ddbTable, err)\n    }\n  }\n  time.Sleep(backoff(i))\n}","preventionTips":["Manage the DDB lock table as Terraform-managed infra; never delete it ad hoc.","Grant dynamodb:GetItem on the table in all roles that read state.","Prefer on-demand billing for the lock table to absorb concurrent reads.","Keep `dynamodb_table` and `region` in sync with the actual table."],"tags":["dynamodb","remote-state","md5","stale-state","iam","consistency"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}