hashicorp/terraform · warning

(lock ID: " / ")

Error message

%s (lock ID: "%s/%s")

What it means

remoteClient.Lock wraps Workspaces.Lock. When TFC returns ErrWorkspaceLocked (already locked), the backend augments the error with the org/workspace lock-ID string so the user knows what to unlock. The augmented error is wrapped in a statemgr.LockError.

Solutions

  1. Wait for the in-flight run/operation to finish and release the lock.
  2. If the lock is stale, run `terraform force-unlock "<org>/<workspace>"` using the ID from the error.
  3. Serialize concurrent runs on the same workspace via TFC queues or external locking.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check lock state to give a friendlier error
ws, _ := c.Workspaces.Read(ctx, org, name)
if ws.Locked { return fmt.Errorf("workspace %s/%s already locked; check active runs", org, name) }

Prevention

When it happens

Trigger: Workspaces.Lock returns tfe.ErrWorkspaceLocked: another `terraform plan/apply`, a TFC run, or a crashed process holds the workspace lock; a previous run died without unlocking.

Common situations: Concurrent CI jobs on the same workspace; long-running TFC apply still in progress; lock left behind after a killed process (requires force-unlock).

Related errors


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

Appendix: source

Thrown at internal/backend/remote/backend_state.go:192

// by implementing remote.ClientForcePusher
func (r *remoteClient) EnableForcePush() {
	r.forcePush = true
}

// Lock the remote state.
func (r *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
	ctx := context.Background()

	lockErr := &statemgr.LockError{Info: r.lockInfo}

	// Lock the workspace.
	_, err := r.client.Workspaces.Lock(ctx, r.workspace.ID, tfe.WorkspaceLockOptions{
		Reason: tfe.String("Locked by Terraform"),
	})
	if err != nil {
		if err == tfe.ErrWorkspaceLocked {
			lockErr.Info = info
			err = fmt.Errorf("%s (lock ID: \"%s/%s\")", err, r.organization, r.workspace.Name)
		}
		lockErr.Err = err
		return "", lockErr
	}

	r.lockInfo = info

	return r.lockInfo.ID, nil
}

// Unlock the remote state.
func (r *remoteClient) Unlock(id string) error {
	ctx := context.Background()

	// We first check if there was an error while uploading the latest
	// state. If so, we will not unlock the workspace to prevent any
	// changes from being applied until the correct state is uploaded.
	if r.stateUploadErr {

View on GitHub (pinned to d32a084675)