hashicorp/terraform · error

Unlocking the state file on TencentCloud cos backend failed

Error message

Unlocking the state file on TencentCloud cos backend failed:

Error message: %v
Lock ID (gen): %s

You may have to force-unlock this state in order to use it again.
The TencentCloud backend acquires a lock during initialization
to ensure the initial state file is created.

What it means

Mirror of 181 for COS. After taking the init lock and failing on RefreshState/WriteState/PersistState, the lockUnlock helper calls stateMgr.Unlock(lockId); if that unlock itself fails (formatted by unlockErrMsg), this error is raised and the user is told to force-unlock.

Source

Thrown at internal/backend/remote-state/cos/backend_state.go:121

			break
		}
	}

	if !exists {
		log.Printf("[DEBUG] workspace %v not exists", name)

		// take a lock on this state while we write it
		lockInfo := statemgr.NewLockInfo()
		lockInfo.Operation = "init"
		lockId, err := c.Lock(lockInfo)
		if err != nil {
			return nil, diags.Append(fmt.Errorf("Failed to lock cos state: %s", err))
		}

		// Local helper function so we can call it multiple places
		lockUnlock := func(e error) error {
			if err := stateMgr.Unlock(lockId); err != nil {
				return fmt.Errorf(unlockErrMsg, err, lockId)
			}
			return e
		}

		// Grab the value
		if err := stateMgr.RefreshState(); err != nil {
			err = lockUnlock(err)
			return nil, diags.Append(err)
		}

		// If we have no state, we have to create an empty state
		if v := stateMgr.State(); v == nil {
			if err := stateMgr.WriteState(states.NewState()); err != nil {
				err = lockUnlock(err)
				return nil, diags.Append(err)
			}
			if err := stateMgr.PersistState(nil); err != nil {
				err = lockUnlock(err)

View on GitHub (pinned to d32a084675)

Solutions

  1. Capture the Lock ID (gen) printed in the message and run `terraform force-unlock <LOCK_ID>`.
  2. Inspect the lock object at the lock file path in the bucket; remove it manually if stale.
  3. Verify COS service status and credentials before retrying.
Defensive patterns

Strategy: try-catch

Try / catch

// If init failed with a dangling lock, attempt recovery once.
sm, diags := backend.StateMgr(name)
if diags.HasErrors() {
    msg := diags.Err().Error()
    if strings.Contains(msg, "force-unlock") {
        if id := extractLockID(msg); id != "" {
            log.Printf("init left dangling lock %s; attempting force-unlock", id)
            if _, uerr := sm.Unlock(id); uerr != nil {
                return fmt.Errorf("manual recovery required: %w", uerr)
            }
        }
    }
    return diags.Err()
}

Prevention

When it happens

Trigger: In StateMgr init, one of RefreshState/WriteState/PersistState errors; lockUnlock's stateMgr.Unlock(lockId) returns non-nil; the helper returns fmt.Errorf(unlockErrMsg, err, lockId).

Common situations: COS connectivity lost mid-init; lock object deleted externally before unlock; COS 5xx during DeleteObject on the lock file; lock id mismatch because the lock was already released or re-acquired.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/f8d18730dc8200ed. Report an issue: GitHub.