hashicorp/terraform · critical

%v Additionally, unlocking the state in Kubernetes faile

Error message

%v
				Additionally, unlocking the state in Kubernetes failed:

				Error message: %q
				Lock ID (gen): %v
				Secret Name: %v

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

What it means

During initial state creation the Kubernetes backend acquires a lock; if a subsequent WriteState/PersistState (or the final cleanup unlock) fails AND the compensating Unlock also fails, both errors are concatenated into this multi-line message. It tells the user the state may be left locked and points at force-unlock as the recovery path.

Source

Thrown at internal/backend/remote-state/kubernetes/backend_state.go:128

		secretName, err := c.createSecretName(0)
		if err != nil {
			return nil, diags.Append(err)
		}

		// Local helper function so we can call it multiple places
		unlock := func(baseErr error) error {
			if err := stateMgr.Unlock(lockID); err != nil {
				const unlockErrMsg = `%v
				Additionally, unlocking the state in Kubernetes failed:

				Error message: %q
				Lock ID (gen): %v
				Secret Name: %v

				You may have to force-unlock this state in order to use it again.
				The Kubernetes backend acquires a lock during initialization to ensure
				the initial state file is created.`
				return fmt.Errorf(unlockErrMsg, baseErr, err.Error(), lockID, secretName)
			}

			return baseErr
		}

		if err := stateMgr.WriteState(states.NewState()); err != nil {
			unlockErr := unlock(err)
			return nil, diags.Append(unlockErr)
		}
		if err := stateMgr.PersistState(nil); err != nil {
			unlockErr := unlock(err)
			return nil, diags.Append(unlockErr)
		}

		// Unlock, the state should now be initialized
		if err := unlock(nil); err != nil {
			return nil, diags.Append(err)
		}

View on GitHub (pinned to c9def3e214)

Solutions

  1. Run `terraform force-unlock <Lock ID (gen)>` using the Lock ID printed in the message to release the stale lock.
  2. Resolve the underlying base error shown at the top of the message (e.g. RBAC/namespace/quota issues for Secret writes).
  3. Verify the lease/secret names shown exist and are not held by another active run before force-unlocking.
  4. Re-run `terraform init`/`terraform plan` once the lock is cleared and the base error is fixed.

Example fix

# before - initial state write failed and left the state locked
terraform init   # fails with the composite unlock error

# after - release the stale lock, then re-run
terraform force-unlock <Lock-ID-from-message>
terraform init
Defensive patterns

Strategy: try-catch

Try / catch

// After a failed init that may leave a k8s lock, parse the lock id and force-unlock
_, diags := b.StateMgr(name)
if diags.HasErrors() {
    if lockID := extractLockID(diags.Err().Error()); lockID != "" {
        // surface to user / run: terraform force-unlock <lockID>
    }
    return diags
}

Prevention

When it happens

Trigger: StateMgr() (backend_state.go:100-148) finds no existing state, takes an init lock, then WriteState or PersistState fails (e.g. secret create/update API error); the deferred unlock() also fails (e.g. lease update conflict), so unlock() returns this composite error wrapping the original baseErr.

Common situations: Kubernetes API transient errors during the secret write for the initial empty state (RBAC, quota, etcd unavailable); the lease object was modified concurrently so the unlock Update hits a conflict; the lease was deleted out-of-band so the unlock's getLease/update fails.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/2c59f4ec3e6841d5. Report an issue: GitHub.