{"record":{"id":"3dd15366d6581510","repo":"hashicorp/terraform","slug":"failed-to-unmarshal-json-data-into-lockinfo-struct","errorCode":null,"errorMessage":"failed to unmarshal JSON data into LockInfo struct: %w","messagePattern":"failed to unmarshal JSON data into LockInfo struct: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oci/client.go","lineNumber":320,"sourceCode":"\t\tNamespaceName: common.String(c.namespace),\n\t\tObjectName:    common.String(c.lockFilePath),\n\t\tBucketName:    common.String(c.bucketName),\n\t\tRequestMetadata: common.RequestMetadata{\n\t\t\tRetryPolicy: getDefaultRetryPolicy(),\n\t\t},\n\t}\n\n\tgetResponse, err := c.objectStorageClient.GetObject(ctx, getRequest)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"failed to get existing lock file: %w\", err)\n\t}\n\tlockByteData, err := io.ReadAll(getResponse.Content)\n\tif err != nil {\n\t\treturn nil, *getResponse.ETag, fmt.Errorf(\"failed to read existing lock file content: %w\", err)\n\t}\n\tlockInfo := &statemgr.LockInfo{}\n\tif err := json.Unmarshal(lockByteData, lockInfo); err != nil {\n\t\treturn lockInfo, \"\", fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w\", err)\n\t}\n\treturn lockInfo, *getResponse.ETag, nil\n}\nfunc (c *RemoteClient) Unlock(id string) error {\n\tctx := context.TODO()\n\tlogger := logWithOperation(\"unlock-state-file\").Named(c.lockFilePath)\n\tlogger.Info(\"unlocking remote state\")\n\tlockInfo, etag, err := c.getLockInfo(ctx)\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to retrieve lock information from OCI Object Storage: %w\", err)\n\t}\n\t// Verify that the provided lock ID matches the lock ID of the retrieved lock file.\n\tif lockInfo.ID != id {\n\t\treturn &statemgr.LockError{\n\t\t\tInfo: lockInfo,\n\t\t\tErr:  fmt.Errorf(\"lock ID '%s' does not match the existing lock ID '%s'\", id, lockInfo.ID),\n\t\t}","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/oci/client.go#L302-L338","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: lock object contains garbage / truncated JSON\noci os object get --bucket-name tf-state --name \"env/prod.tflock\" --file -\n#   > {\"ID\":\"abc\",\"Op\n# after: confirm no operation is active, then remove the corrupt lock\noci os object delete --bucket-name tf-state --name \"env/prod.tflock\"\n# then re-run the terraform operation","handlingStrategy":"validation","validationCode":"// Sanity-check the lock object before trusting it\nfunc parseLockInfo(c *RemoteClient, ctx context.Context) (*statemgr.LockInfo, error) {\n    resp, err := c.objectStorageClient.GetObject(ctx, /* lock request */)\n    if err != nil { return nil, err }\n    b, _ := io.ReadAll(resp.Content)\n    if !json.Valid(b) {\n        return nil, fmt.Errorf(\"lock object is not valid JSON — manual recovery required\")\n    }\n    li := &statemgr.LockInfo{}\n    return li, json.Unmarshal(b, li)\n}","typeGuard":"func isJSONError(err error) bool {\n    var se *json.SyntaxError\n    return errors.As(err, &se)\n}","tryCatchPattern":"// On unmarshal failure, escalate to manual recovery instead of looping\nif err := json.Unmarshal(lockByteData, lockInfo); err != nil {\n    return lockInfo, \"\", fmt.Errorf(\"failed to unmarshal JSON data into LockInfo struct: %w\", err)\n}","preventionTips":["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."],"tags":["oci","state-locking","serialization","data-integrity"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}