hashicorp/terraform · error

Failed to lock cos state

Error message

Failed to lock cos state: %s

What it means

Mirror of error 180 for the COS backend. During StateMgr init for a workspace that does not yet exist, the backend acquires a lock in order to atomically create an empty state. If remoteClient.Lock returns an error, it is wrapped here.

Solutions

  1. Capture the Lock ID (md5) from the wrapped error and run `terraform force-unlock <LOCK_ID>`.
  2. Inspect the lock object in the bucket at the lock file path; remove it manually if force-unlock cannot reach it.
  3. Verify COS credentials have PutObject/GetObject/DeleteObject on both state and lock key prefixes.
  4. Avoid concurrent runs against the same workspace.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check whether a COS lock object already exists for the workspace.
func cosLockExists(c *remoteClient) (bool, error) {
    exists, _, _, err := c.getObject(c.lockFile)
    if err != nil {
        return false, err
    }
    return exists, nil
}

Try / catch

// Catch the lock failure, surface the lock ID, offer force-unlock.
sm, diags := backend.StateMgr(name)
if diags.HasErrors() {
    msg := diags.Err().Error()
    if strings.Contains(msg, "Failed to lock cos state") {
        if id := extractLockID(msg); id != "" {
            return fmt.Errorf("state %q locked (id=%s); run terraform force-unlock %s", name, id, id)
        }
    }
    return diags.Err()
}

Prevention

When it happens

Trigger: Backend.StateMgr(name) where name is not in Workspaces(); c.Lock(lockInfo) returns an error — typically cosLock fails, the lock file already exists, or COS putObject of the lock info fails.

Common situations: A stale lock object in the COS bucket from a previous crashed run; a concurrent Terraform run holding the lock; COS credentials lack PutObject on the lock file key; network/COS 5xx.

Related errors


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

Appendix: source

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

	}

	exists := false
	for _, candidate := range ws {
		if candidate == name {
			exists = true
			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 {

View on GitHub (pinned to d32a084675)