{"record":{"id":"d1f1e165803cdefd","repo":"hashicorp/terraform","slug":"s-lock-id-s-s-d1f1e1","errorCode":null,"errorMessage":"%s (lock ID: \"%s/%s\")","messagePattern":"(.+?) \\(lock ID: \"(.+?)/(.+?)\"\\)","errorType":"exception","errorClass":"statemgr.LockError","httpStatus":null,"severity":"warning","filePath":"internal/cloud/state.go","lineNumber":352,"sourceCode":"func (s *State) Lock(info *statemgr.LockInfo) (string, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif s.disableLocks {\n\t\treturn \"\", nil\n\t}\n\tctx := context.Background()\n\n\tlockErr := &statemgr.LockError{Info: s.lockInfo}\n\n\t// Lock the workspace.\n\t_, err := s.tfeClient.Workspaces.Lock(ctx, s.workspace.ID, tfe.WorkspaceLockOptions{\n\t\tReason: tfe.String(\"Locked by Terraform\"),\n\t})\n\tif err != nil {\n\t\tif err == tfe.ErrWorkspaceLocked {\n\t\t\tlockErr.Info = info\n\t\t\terr = fmt.Errorf(\"%s (lock ID: \\\"%s/%s\\\")\", err, s.organization, s.workspace.Name)\n\t\t}\n\t\tlockErr.Err = err\n\t\treturn \"\", lockErr\n\t}\n\n\ts.lockInfo = info\n\n\treturn s.lockInfo.ID, nil\n}\n\n// statemgr.Refresher impl.\nfunc (s *State) RefreshState() error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\treturn s.refreshState()\n}\n\n// refreshState is the main implementation of RefreshState, but split out so","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/cloud/state.go#L334-L370","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait for the in-flight run to finish, then retry.","In the HCP/TFE UI, confirm what holds the lock.","If orphaned, run terraform force-unlock \"<org>/<workspace>\" using the lock ID shown.","Serialize CI jobs that target the workspace."],"exampleFix":"# before: two pipelines apply the same workspace at once -> lock contention\n# after: force-unlock the orphaned lock, then run serially\nterraform force-unlock \"my-org/my-workspace\"\nterraform apply","handlingStrategy":"retry","validationCode":"// Check whether the workspace is already locked before acquiring\nws, err := client.Workspaces.Read(ctx, org, name)\nif err == nil && ws.Locked {\n    return fmt.Errorf(\"workspace already locked (lock ID: %s/%s); wait or force-unlock\", org, name)\n}","typeGuard":"func isWorkspaceAlreadyLocked(err error) bool {\n    return errors.Is(err, tfe.ErrWorkspaceLocked)\n}","tryCatchPattern":"if _, err := state.Lock(info); err != nil {\n    var lockErr *statemgr.LockError\n    if errors.As(err, &lockErr) && errors.Is(lockErr.Err, tfe.ErrWorkspaceLocked) {\n        // back off and retry a bounded number of times, or surface force-unlock guidance\n    }\n}","preventionTips":["Serialize CI jobs per workspace","Inspect the HCP UI before force-unlocking","Ensure errored runs release their locks"],"tags":["locking","concurrency","workspace","contention"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}