{"record":{"id":"6bbc6e2a9a55006a","repo":"hashicorp/terraform","slug":"failed-to-retrieve-lock-information-from-oci-objec","errorCode":null,"errorMessage":"Failed to retrieve lock information from OCI Object Storage: %w","messagePattern":"Failed to retrieve lock information from OCI Object Storage: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/oci/client.go","lineNumber":331,"sourceCode":"\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}\n\t}\n\n\tdeleteRequest := objectstorage.DeleteObjectRequest{\n\t\tNamespaceName: common.String(c.namespace),\n\t\tObjectName:    common.String(c.lockFilePath),\n\t\tBucketName:    common.String(c.bucketName),\n\t\tIfMatch:       common.String(etag),\n\t\tRequestMetadata: common.RequestMetadata{\n\t\t\tRetryPolicy: getDefaultRetryPolicy(),\n\t\t},\n\t}","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/oci/client.go#L313-L349","documentation":"Unlock wraps any error returned by getLockInfo with this message (note the inconsistent capital 'Failed' versus sibling errors). It signals that lock information could not be retrieved before the delete was attempted. The wrapped %w is one of errors 310, 311, or 312 and should be inspected for the real cause.","triggerScenarios":"Any getLockInfo failure during Unlock: 404/403 on the lock object (310), read-content stream failure (311), or JSON unmarshal failure (312).","commonSituations":"Recovery scenarios after a corrupted or manually edited lock; concurrent unlockers; permission or network issues during the unlock attempt.","solutions":["Unwrap the error (errors.Unwrap / errors.As) to find the underlying 310/311/312 cause and address that specifically.","For 404 underlying, the lock is already gone — treat the unlock as complete.","For 412/unmarshal underlying, force-unlock by deleting the lock object after confirming no active operation.","Run with TF_LOG=DEBUG to see the original getLockInfo error alongside this wrapper."],"exampleFix":"// before: surface only the opaque wrapper\nreturn err // \"Failed to retrieve lock information...\"\n// after: unwrap to drive behavior\nvar se common.ServiceError\nswitch {\ncase errors.As(errors.Unwrap(err), &se) && se.GetHTTPStatusCode() == 404:\n    return nil // lock already gone\ndefault:\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func underlyingStatus(err error) (int, bool) {\n    var se common.ServiceError\n    if errors.As(err, &se) { return se.GetHTTPStatusCode(), true }\n    return 0, false\n}","tryCatchPattern":"// Unwrap to drive behavior rather than treating the opaque wrapper\nif err := c.Unlock(id); err != nil {\n    if status, ok := underlyingStatus(errors.Unwrap(err)); ok && status == 404 {\n        return nil // lock already gone\n    }\n    return err\n}","preventionTips":["Always inspect the wrapped %w, not just this wrapper, before deciding recovery.","Run with TF_LOG=DEBUG to capture the original getLockInfo error.","Standardize error-message capitalization across the file (this one differs from siblings).","Document the recovery path for each underlying cause (310/311/312)."],"tags":["oci","state-locking","error-recovery"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}