hashicorp/terraform · error
Unable to retrieve item from DynamoDB table
Error message
Unable to retrieve item from DynamoDB table %q: %w
What it means
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.
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.
Example fix
# before: stale/missing ddb table name
backend "s3" {
bucket = "tf-state-prod"
dynamodb_table = "terraform-locks-gone"
}
# after
backend "s3" {
bucket = "tf-state-prod"
dynamodb_table = "terraform-locks" # existing table
region = "us-west-2"
} Defensive patterns
Strategy: retry
Validate before calling
// Before reading state, confirm the DDB digest table is reachable.
func ddbTableReachable(ctx context.Context, c *dynamodb.Client, table string) error {
if _, err := c.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: &table}); err != nil {
return fmt.Errorf("dynamodb table %s not reachable: %w", table, err)
}
return nil
} Try / catch
// Retry transient GetItem; fail fast on ResourceNotFound/AccessDenied.
for i := 0; i < 3; i++ {
resp, err := c.dynClient.GetItem(ctx, getParams)
if err == nil { /* process resp */ return sum, nil }
var apiErr smithy.APIError
if errors.As(err, &apiErr) {
if apiErr.ErrorCode() == "ResourceNotFoundException" || apiErr.ErrorCode() == "AccessDenied" {
return nil, fmt.Errorf("Unable to retrieve item from DynamoDB table %q: %w", c.ddbTable, err)
}
}
time.Sleep(backoff(i))
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to store state MD5
- Unable to delete item from DynamoDB table
- failed to clean up file lock after DynamoDB lock error
- failed to retrieve lock info for lock ID
- failed to unlock both S3 and DynamoDB: S3 error
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/890d28cacf81ac7c.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/s3/client.go:609
func (c *RemoteClient) getMD5(ctx context.Context) ([]byte, error) {
if c.ddbTable == "" {
return nil, nil
}
getParams := &dynamodb.GetItemInput{
Key: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath() + stateIDSuffix,
},
},
ProjectionExpression: aws.String("LockID, Digest"),
TableName: aws.String(c.ddbTable),
ConsistentRead: aws.Bool(true),
}
resp, err := c.dynClient.GetItem(ctx, getParams)
if err != nil {
return nil, fmt.Errorf("Unable to retrieve item from DynamoDB table %q: %w", c.ddbTable, err)
}
var val string
if v, ok := resp.Item["Digest"]; ok {
if v, ok := v.(*dynamodbtypes.AttributeValueMemberS); ok {
val = v.Value
}
}
sum, err := hex.DecodeString(val)
if err != nil || len(sum) != md5.Size {
return nil, errors.New("invalid md5")
}
return sum, nil
}
// store the hash of the state so that clients can check for stale state files.View on GitHub (pinned to d32a084675)