hashicorp/terraform · error
object is empty
Error message
object %q is empty
What it means
The state object exists (Head succeeded, Get succeeded, MD5 was computed) but its body is zero bytes. The backend refuses to return a nil/empty payload and instead surfaces this corruption error so that init does not silently treat the workspace as new.
Solutions
- Restore the state from a backup or, if object versioning is enabled, promote a previous non-empty version.
- If the state is genuinely meant to be empty/absent, delete the object so init recreates it as a proper empty state.
- Enable Object Versioning on the bucket so future corruption is recoverable.
- Audit who/what has OBJECT_OVERWRITE on the bucket and remove stray writers.
Example fix
# before: someone truncated the state object oci os object head --bucket-name tf-state --name prod.tfstate # Content-Length: 0 # after: restore from a prior version, then re-run init oci os object-version copy-to ... # or restore latest non-empty version # or, if state is intentionally empty, delete the object: oci os object delete --bucket-name tf-state --name prod.tfstate
Defensive patterns
Strategy: validation
Validate before calling
// Treat a zero-length object as corruption and refuse to proceed silently
func assertNonEmpty(p *remote.Payload, path string) error {
if p != nil && len(p.Data) == 0 {
return fmt.Errorf("object %q is empty — restore from backup/versioning or delete and re-init", path)
}
return nil
} Try / catch
// On empty-object error, prompt recovery rather than silent retry
if err != nil && strings.Contains(err.Error(), "is empty") {
// restore from object versioning or delete the object so init recreates it
} Prevention
- Enable Object Versioning on the state bucket so corruption is recoverable.
- Restrict OBJECT_OVERWRITE to the Terraform principal only — no stray writers.
- Audit the bucket for zero-byte state objects periodically.
- Back up state outside the bucket (e.g. scheduled object copy).
When it happens
Trigger: Someone manually created or truncated the object at the configured key; a previous interrupted write left a zero-byte object; bucket replication/lifecycle created an empty placeholder; an external process wrote an empty file to the same key.
Common situations: Manual bucket tampering; a failed migration from another backend that wrote the empty target object; cross-region replication in progress producing a placeholder; object versioning confusion where the 'current' version is empty.
Related errors
- failed to access object HttpStatusCode
- failed to access object
- failed to get existing lock file
- failed to lock oci state
- failed to unmarshal JSON data into LockInfo struct
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/4ba763422d441f44.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/oci/client.go:123
contentArray, err := io.ReadAll(getResponse.Content)
if err != nil {
return nil, fmt.Errorf("unable to read 'content' from response: %w", err)
}
// Compute MD5 hash
md5Hash := getResponse.ContentMd5
if md5Hash == nil || len(*md5Hash) == 0 {
md5Hash = getResponse.OpcMultipartMd5
}
// Construct payload
payload := &remote.Payload{
Data: contentArray,
MD5: []byte(*md5Hash),
}
// Return an error instead of `nil, nil` if the object is empty
if len(payload.Data) == 0 {
return nil, fmt.Errorf("object %q is empty", c.path)
}
return payload, nil
}
func (c *RemoteClient) Put(data []byte) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
logger := logWithOperation("upload-state-file").Named(c.path)
ctx := context.WithValue(context.Background(), "logger", logger)
dataSize := int64(len(data))
sum := md5.Sum(data)
var err error
if dataSize > DefaultFilePartSize {
logger.Info("Using Multipart Feature")
var multipartUploadData = MultipartUploadData{
client: c,
Data: data,View on GitHub (pinned to d32a084675)