hashicorp/terraform · warning · statemgr.LockError

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

Error message

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

What it means

Lock calls Workspaces.Lock; when the API returns tfe.ErrWorkspaceLocked the workspace is already held by another run/process. The error is augmented with the lock ID "<organization>/<workspace-name>" so the holder can be identified or force-unlocked.

Source

Thrown at internal/cloud/state.go:352

func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.disableLocks {
		return "", nil
	}
	ctx := context.Background()

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

	// Lock the workspace.
	_, err := s.tfeClient.Workspaces.Lock(ctx, s.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, s.organization, s.workspace.Name)
		}
		lockErr.Err = err
		return "", lockErr
	}

	s.lockInfo = info

	return s.lockInfo.ID, nil
}

// statemgr.Refresher impl.
func (s *State) RefreshState() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.refreshState()
}

// refreshState is the main implementation of RefreshState, but split out so

View on GitHub (pinned to c9def3e214)

Solutions

  1. Wait for the in-flight run to finish, then retry.
  2. In the HCP/TFE UI, confirm what holds the lock.
  3. If orphaned, run terraform force-unlock "<org>/<workspace>" using the lock ID shown.
  4. Serialize CI jobs that target the workspace.

Example fix

# before: two pipelines apply the same workspace at once -> lock contention
# after: force-unlock the orphaned lock, then run serially
terraform force-unlock "my-org/my-workspace"
terraform apply
Defensive patterns

Strategy: retry

Validate before calling

// Check whether the workspace is already locked before acquiring
ws, err := client.Workspaces.Read(ctx, org, name)
if err == nil && ws.Locked {
    return fmt.Errorf("workspace already locked (lock ID: %s/%s); wait or force-unlock", org, name)
}

Type guard

func isWorkspaceAlreadyLocked(err error) bool {
    return errors.Is(err, tfe.ErrWorkspaceLocked)
}

Try / catch

if _, err := state.Lock(info); err != nil {
    var lockErr *statemgr.LockError
    if errors.As(err, &lockErr) && errors.Is(lockErr.Err, tfe.ErrWorkspaceLocked) {
        // back off and retry a bounded number of times, or surface force-unlock guidance
    }
}

Prevention

When it happens

Trigger: Two Terraform operations target the same HCP/TFE workspace concurrently, a previous run crashed leaving the workspace locked, or a manual/UI lock is held.

Common situations: CI pipelines running in parallel against one workspace, a prior apply that errored and left the lock orphaned, or someone locked the workspace in the HCP UI.

Related errors


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