{"record":{"id":"27812425c9282579","repo":"hashicorp/terraform","slug":"lock-file-s-not-exists","errorCode":null,"errorMessage":"lock file %s not exists","messagePattern":"lock file (.+?) not exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/cos/client.go","lineNumber":168,"sourceCode":"\tinfo, infoErr := c.lockInfo()\n\tif infoErr != nil {\n\t\tlockErr.Err = errors.Join(lockErr.Err, infoErr)\n\t} else {\n\t\tlockErr.Info = info\n\t}\n\n\treturn lockErr\n}\n\n// lockInfo returns LockInfo from lock file\nfunc (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {\n\texists, data, checksum, err := c.getObject(c.lockFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"lock file %s not exists\", c.lockFile)\n\t}\n\n\tinfo := &statemgr.LockInfo{}\n\tif err := json.Unmarshal(data, info); err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo.ID = checksum\n\n\treturn info, nil\n}\n\n// getObject get remote object\nfunc (c *remoteClient) getObject(cosFile string) (exists bool, data []byte, checksum string, err error) {\n\trsp, err := c.cosClient.Object.Get(c.cosContext, cosFile, nil)\n\tif rsp == nil {\n\t\tlog.Printf(\"[DEBUG] getObject %s: error: %v\", cosFile, err)\n\t\terr = fmt.Errorf(\"failed to open file at %v: %v\", cosFile, err)","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/cos/client.go#L150-L186","documentation":"Thrown by the COS remote-state backend's lockInfo() helper when the lock object is queried (exists==false) with no transport error. It means the lock file the backend expects to find in the Tencent COS bucket is absent, so the stored LockInfo cannot be read. This typically surfaces inside Unlock()/lockError() paths when Terraform tries to reconcile a lock that was never written, was already removed, or was deleted out of band.","triggerScenarios":"Calling remoteClient.Unlock() (which calls lockInfo()) when no lock object exists at c.lockFile in the COS bucket; or lockError() trying to enrich a prior failure by reading a lock file that is gone. Also reached if a concurrent run/another process/manual deletion removed the .tflock file between Lock() and Unlock().","commonSituations":"A previous `terraform apply` crashed or was killed before writing the lock file; someone manually deleted the lock object from the COS bucket; the lock file path (state/lock prefix) was changed in backend config between operations; a stale lock ID is being passed to `terraform force-unlock`.","solutions":["Verify the lock object exists in the bucket: list objects under the configured prefix/key (default is `<state key>.tflock`) via the COS console or API.","If the lock file truly is gone but Terraform still thinks state is locked, run `terraform force-unlock <LOCK_ID>` with the ID from the prior lock attempt.","Confirm the backend `key`/`state` and `lock_file` (or prefix) settings match the run that originally locked the state.","If another run legitimately holds the lock, wait for it to finish or have it release the lock rather than deleting the file manually."],"exampleFix":"// before: backend block with mismatched key leading to wrong lock path\nbackend \"cos\" { bucket=\"tf-state\"; key=\"prod/terraform.tfstate\"; lock_file=\"prod/terraform.tfstate.tflock\" }\n// after: ensure lock_file path aligns with the actual object key and remove stale references\nbackend \"cos\" { bucket=\"tf-state\"; key=\"prod/terraform.tfstate\" }  // lock_file defaults to <key>.tflock","handlingStrategy":"validation","validationCode":"// Before attempting Unlock, check the lock object exists via the same backend\n// (run from a small Go helper or a terraform command):\n//   terraform state list  # will surface the lock state during plan\n// In custom automation wrapping Terraform, only call force-unlock when the\n// lock file is verifiably absent:\nfunc lockFileExists(cosClient *cos.Client, ctx context.Context, lockFile string) (bool, error) {\n    rsp, err := cosClient.Object.Get(ctx, lockFile, nil)\n    if err == nil && rsp != nil && rsp.StatusCode == 200 {\n        rsp.Body.Close()\n        return true, nil\n    }\n    if rsp != nil && rsp.StatusCode == 404 {\n        rsp.Body.Close()\n        return false, nil\n    }\n    return false, err\n}","typeGuard":"// Guard a force-unlock decision on the verifiable absence of the lock object\nfunc shouldForceUnlock(cosClient *cos.Client, ctx context.Context, lockFile string) bool {\n    exists, err := lockFileExists(cosClient, ctx, lockFile)\n    return err == nil && !exists\n}","tryCatchPattern":"// Go callers of the COS backend should treat errors.Is on the wrapped message\n// or, better, check statemgr.LockError type:\nif errors.As(err, &lockErr *statemgr.LockError) {\n    // inspect lockErr.Info; if nil and message contains 'not exists', the lock\n    // file is already gone — treat unlock as best-effort/no-op\n}","preventionTips":["Always let Terraform manage the lock object; never delete the .tflock file manually mid-run.","Keep the backend `key` and any `lock_file` stable across runs that touch the same state.","After a crash, inspect the lock object before forcing unlock so you don't clobber a live lock.","Use `terraform force-unlock <ID>` with the exact ID rather than removing state files."],"tags":["terraform","cos","tencent-cloud","state-lock","remote-state","go"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}