hashicorp/terraform · error

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

Error message

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

What it means

Emitted by remoteClient.Lock (backend_state.go:189-196) when Workspaces.Lock fails specifically with tfe.ErrWorkspaceLocked. The message enriches the underlying 'workspace is already locked' error with the canonical lock ID formed as "organization/workspace" so the user knows which workspace holds the lock and can force-unlock if needed.

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 c9def3e214)

Solutions

  1. Confirm no operation is genuinely running, then run `terraform force-unlock "<organization>/<workspace>"` using the lock ID from the message.
  2. If another operation is legitimately running, wait for it to finish rather than force-unlocking.
  3. Investigate why the previous lock was not released (crashed CI, killed process) to prevent recurrence.
  4. Use `-lock-timeout=<duration>` so Terraform waits and retries instead of failing immediately.

Example fix

// before
Error: workspace is already locked (lock ID: "myorg/dev")

// after: confirm no live run, then force-unlock with the shown ID
$ terraform force-unlock "myorg/dev"
Defensive patterns

Strategy: try-catch

Validate before calling

// Preflight: fail fast if the workspace is already locked.
if ws, err := r.client.Workspaces.Read(ctx, r.organization, r.workspace.Name); err == nil && ws.Locked {
    return fmt.Errorf("workspace %s/%s already locked; run force-unlock if stale", r.organization, r.workspace.Name)
}

Try / catch

// On 'already locked', surface the canonical lock id so the operator can force-unlock.
_, err := r.client.Workspaces.Lock(ctx, r.workspace.ID, tfe.WorkspaceLockOptions{Reason: tfe.String("Locked by Terraform")})
if err != nil {
    if err == tfe.ErrWorkspaceLocked {
        err = fmt.Errorf("%s (lock ID: %q/%q)", err, r.organization, r.workspace.Name)
    }
    return "", &statemgr.LockError{Info: info, Err: err}
}

Prevention

When it happens

Trigger: Acquiring a state lock when the workspace is already locked — a previous run/process crashed without unlocking, another team member holds the lock, or a prior operation set stateUploadErr leaving the lock in place.

Common situations: CI runner killed mid-apply leaving a stale lock; local `terraform apply` interrupted (Ctrl-C) before unlock; previous state upload error deliberately retained the lock (see error 427); two developers running against the same workspace concurrently.

Related errors


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