hashicorp/terraform · error
lock file not exists
Error message
lock file %s not exists
What it means
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.
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.
Example fix
// before: backend block with mismatched key leading to wrong lock path
backend "cos" { bucket="tf-state"; key="prod/terraform.tfstate"; lock_file="prod/terraform.tfstate.tflock" }
// after: ensure lock_file path aligns with the actual object key and remove stale references
backend "cos" { bucket="tf-state"; key="prod/terraform.tfstate" } // lock_file defaults to <key>.tflock Defensive patterns
Strategy: validation
Validate before calling
// Before attempting Unlock, check the lock object exists via the same backend
// (run from a small Go helper or a terraform command):
// terraform state list # will surface the lock state during plan
// In custom automation wrapping Terraform, only call force-unlock when the
// lock file is verifiably absent:
func lockFileExists(cosClient *cos.Client, ctx context.Context, lockFile string) (bool, error) {
rsp, err := cosClient.Object.Get(ctx, lockFile, nil)
if err == nil && rsp != nil && rsp.StatusCode == 200 {
rsp.Body.Close()
return true, nil
}
if rsp != nil && rsp.StatusCode == 404 {
rsp.Body.Close()
return false, nil
}
return false, err
} Type guard
// Guard a force-unlock decision on the verifiable absence of the lock object
func shouldForceUnlock(cosClient *cos.Client, ctx context.Context, lockFile string) bool {
exists, err := lockFileExists(cosClient, ctx, lockFile)
return err == nil && !exists
} Try / catch
// Go callers of the COS backend should treat errors.Is on the wrapped message
// or, better, check statemgr.LockError type:
if errors.As(err, &lockErr *statemgr.LockError) {
// inspect lockErr.Info; if nil and message contains 'not exists', the lock
// file is already gone — treat unlock as best-effort/no-op
} Prevention
- 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.
When it happens
Trigger: 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().
Common situations: 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`.
Related errors
- failed to create tag
- failed to delete tag
- bucket not exists
- failed to create bucket
- failed to delete bucket
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/27812425c9282579.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/cos/client.go:168
info, infoErr := c.lockInfo()
if infoErr != nil {
lockErr.Err = errors.Join(lockErr.Err, infoErr)
} else {
lockErr.Info = info
}
return lockErr
}
// lockInfo returns LockInfo from lock file
func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
exists, data, checksum, err := c.getObject(c.lockFile)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("lock file %s not exists", c.lockFile)
}
info := &statemgr.LockInfo{}
if err := json.Unmarshal(data, info); err != nil {
return nil, err
}
info.ID = checksum
return info, nil
}
// getObject get remote object
func (c *remoteClient) getObject(cosFile string) (exists bool, data []byte, checksum string, err error) {
rsp, err := c.cosClient.Object.Get(c.cosContext, cosFile, nil)
if rsp == nil {
log.Printf("[DEBUG] getObject %s: error: %v", cosFile, err)
err = fmt.Errorf("failed to open file at %v: %v", cosFile, err)View on GitHub (pinned to d32a084675)