{"record":{"id":"de1dd39cc9242baf","repo":"anomalyco/sst","slug":"failed-to-delete-state-object-s-version-s-s","errorCode":null,"errorMessage":"failed to delete state object %s version %s: %s","messagePattern":"failed to delete state object (.+?) version (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/project/provider/aws.go","lineNumber":789,"sourceCode":"\treturn ids\n}\n\nfunc (a *AwsHome) deleteObjectVersions(s3Client *s3.Client, bucket string, ids []s3types.ObjectIdentifier) error {\n\tfor i := 0; i < len(ids); i += 1000 {\n\t\tend := i + 1000\n\t\tif end > len(ids) {\n\t\t\tend = len(ids)\n\t\t}\n\t\tout, err := s3Client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{\n\t\t\tBucket: aws.String(bucket),\n\t\t\tDelete: &s3types.Delete{Objects: ids[i:end], Quiet: aws.Bool(true)},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(out.Errors) > 0 {\n\t\t\titem := out.Errors[0]\n\t\t\treturn fmt.Errorf(\"failed to delete state object %s version %s: %s\", aws.ToString(item.Key), aws.ToString(item.VersionId), aws.ToString(item.Message))\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *AwsHome) purge(app, stage string) error {\n\tbootstrap, err := a.provider.Bootstrap(a.provider.config.Region)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts3Client := s3.NewFromConfig(a.provider.config)\n\n\tprefixes := []string{\n\t\ta.pathForData(\"app\", app, stage),\n\t\ta.pathForData(\"secret\", app, stage),\n\t\tpath.Join(\"update\", app, stage) + \"/\",\n\t\tpath.Join(\"summary\", app, stage) + \"/\",\n\t\tpath.Join(\"eventlog\", app, stage) + \"/\",","sourceCodeStart":771,"sourceCodeEnd":807,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/pkg/project/provider/aws.go#L771-L807","documentation":"`deleteObjectVersions` batch-deletes S3 object versions (used by `pruneNoncurrentVersions` and `purge` when resetting stage state). AWS `DeleteObjects` returns per-object errors in the response body rather than via the Go error, so when `out.Errors` is non-empty SST surfaces the first one with the key, version ID, and AWS message.","triggerScenarios":"Purging a stage (`sst purge`/unlock flow) or pruning noncurrent versions when at least one object version cannot be deleted — e.g. `AccessDenied`, version-controlled object protected by policy, or retention/legal hold.","commonSituations":"Bucket policies denying `s3:DeleteObjectVersion`; S3 Object Lock retention on state files; IAM missing `s3:DeleteObjectVersion` while having `s3:DeleteObject`.","solutions":["Read the `%s` message suffix for the concrete AWS reason (AccessDenied, InvalidRequest, etc.) and fix accordingly","Grant the credentials `s3:DeleteObjectVersion` on the state bucket and its contents","If Object Lock/retention is set, wait out or remove the retention before purging"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"aws iam simulate-principal-policy --policy-source-arn <deploy-role-arn> --action-names s3:DeleteObjectVersion s3:DeleteObject --resource-arns arn:aws:s3:::sst-state-bucket/*","typeGuard":null,"tryCatchPattern":"err := sstPurge(ctx, app, stage)\nif err != nil && strings.Contains(err.Error(), \"failed to delete state object\") {\n    if strings.Contains(err.Error(), \"AccessDenied\") {\n        // fix IAM, then retry once\n        err = sstPurge(ctx, app, stage)\n    }\n}","preventionTips":["Grant s3:DeleteObjectVersion in addition to s3:DeleteObject for the state bucket","Don't enable Object Lock/retention on SST state buckets","Check the AWS message suffix in the error for the exact cause before retrying"],"tags":["aws","s3","object-versions","state-purge"],"backgroundTag":"s3-delete-object-version-denied","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}