hashicorp/terraform · error
failed to unmarshal JSON data into LockInfo struct
Error message
failed to unmarshal JSON data into LockInfo struct: %w
What it means
The lock-file body was read successfully but is not valid JSON or does not match the statemgr.LockInfo schema. The lock file is corrupt or has been overwritten with non-lock content at the same key.
Solutions
- Inspect the lock object content in the bucket — if it is not a real lock, it is safe to force-unlock by deleting it.
- Restore a prior non-corrupt version if Object Versioning is enabled.
- Ensure nothing else writes to the configured lock key prefix.
- After cleanup, retry the original operation which will recreate a valid lock.
Example fix
# before: lock object contains garbage / truncated JSON
oci os object get --bucket-name tf-state --name "env/prod.tflock" --file -
# > {"ID":"abc","Op
# after: confirm no operation is active, then remove the corrupt lock
oci os object delete --bucket-name tf-state --name "env/prod.tflock"
# then re-run the terraform operation Defensive patterns
Strategy: validation
Validate before calling
// Sanity-check the lock object before trusting it
func parseLockInfo(c *RemoteClient, ctx context.Context) (*statemgr.LockInfo, error) {
resp, err := c.objectStorageClient.GetObject(ctx, /* lock request */)
if err != nil { return nil, err }
b, _ := io.ReadAll(resp.Content)
if !json.Valid(b) {
return nil, fmt.Errorf("lock object is not valid JSON — manual recovery required")
}
li := &statemgr.LockInfo{}
return li, json.Unmarshal(b, li)
} Type guard
func isJSONError(err error) bool {
var se *json.SyntaxError
return errors.As(err, &se)
} Try / catch
// On unmarshal failure, escalate to manual recovery instead of looping
if err := json.Unmarshal(lockByteData, lockInfo); err != nil {
return lockInfo, "", fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
} Prevention
- Reserve the lock key prefix exclusively for Terraform locks — no other writers.
- Enable Object Versioning so a corrupt lock can be restored.
- Never hand-edit lock objects; use force-unlock only.
- Audit cross-team tools that may write to the same bucket prefix.
When it happens
Trigger: A non-lock object was placed at the lockFilePath; a partial/interrupted write left truncated JSON; an older or incompatible LockInfo schema; binary content mistakenly uploaded to the lock key.
Common situations: An external tool wrote a marker file at the same key; a fork changed LockInfo schema without migration; manual JSON edit that introduced a syntax error; bucket replication overwrote the lock with a different object.
Related errors
- Error unlocking oci state. Lock ID
- failed to get existing lock file
- failed to lock oci state
- failed to read existing lock file content
- Failed to retrieve lock information from OCI Object Storage
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/3dd15366d6581510.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/oci/client.go:320
NamespaceName: common.String(c.namespace),
ObjectName: common.String(c.lockFilePath),
BucketName: common.String(c.bucketName),
RequestMetadata: common.RequestMetadata{
RetryPolicy: getDefaultRetryPolicy(),
},
}
getResponse, err := c.objectStorageClient.GetObject(ctx, getRequest)
if err != nil {
return nil, "", fmt.Errorf("failed to get existing lock file: %w", err)
}
lockByteData, err := io.ReadAll(getResponse.Content)
if err != nil {
return nil, *getResponse.ETag, fmt.Errorf("failed to read existing lock file content: %w", err)
}
lockInfo := &statemgr.LockInfo{}
if err := json.Unmarshal(lockByteData, lockInfo); err != nil {
return lockInfo, "", fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
}
return lockInfo, *getResponse.ETag, nil
}
func (c *RemoteClient) Unlock(id string) error {
ctx := context.TODO()
logger := logWithOperation("unlock-state-file").Named(c.lockFilePath)
logger.Info("unlocking remote state")
lockInfo, etag, err := c.getLockInfo(ctx)
if err != nil {
return fmt.Errorf("Failed to retrieve lock information from OCI Object Storage: %w", err)
}
// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
if lockInfo.ID != id {
return &statemgr.LockError{
Info: lockInfo,
Err: fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID),
}View on GitHub (pinned to d32a084675)