{"record":{"id":"9c091f85eff90b63","repo":"hashicorp/terraform","slug":"state-q-already-locked","errorCode":null,"errorMessage":"state %q already locked","messagePattern":"state %q already locked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/consul/client.go","lineNumber":382,"sourceCode":"\tdefer c.mu.Unlock()\n\n\tif !c.lockState {\n\t\treturn \"\", nil\n\t}\n\n\tc.info = info\n\n\t// These checks only are to ensure we strictly follow the specification.\n\t// Terraform shouldn't ever re-lock, so provide errors for the 2 possible\n\t// states if this is called.\n\tselect {\n\tcase <-c.lockCh:\n\t\t// We had a lock, but lost it.\n\t\treturn \"\", errors.New(\"lost consul lock, cannot re-lock\")\n\tdefault:\n\t\tif c.lockCh != nil {\n\t\t\t// we have an active lock already\n\t\t\treturn \"\", fmt.Errorf(\"state %q already locked\", c.Path)\n\t\t}\n\t}\n\n\treturn c.lock()\n}\n\n// the lock implementation.\n// Only to be called while holding Client.mu\nfunc (c *RemoteClient) lock() (string, error) {\n\t// We create a new session here, so it can be canceled when the lock is\n\t// lost or unlocked.\n\tlockSession, err := c.createSession()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// store the session ID for correlation with consul logs\n\tc.info.Info = \"consul session: \" + lockSession","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/consul/client.go#L364-L400","documentation":"Raised by RemoteClient.Lock() (consul/client.go:382) when Lock() is called while the client already holds an active lock (c.lockCh != nil and not closed). Terraform is designed to lock exactly once per operation, so hitting this is a state-machine violation in the calling code, not a normal user condition.","triggerScenarios":"Lock() invoked a second time on the same RemoteClient instance without an intervening Unlock(); the default branch in the select sees c.lockCh != nil.","commonSituations":"A bug in calling code that reuses a RemoteClient and double-locks; a wrapper/orchestrator that calls Lock twice; extremely unusual for stock terraform CLI.","solutions":["Ensure Lock and Unlock are strictly paired in the calling code; never Lock an already-locked client.","If seen from the stock CLI, report as a terraform bug with the operation sequence.","Restart the terraform process to reset client state and retry the operation.","Audit any custom orchestration that drives the backend for a double-Lock path."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure Lock is only called when no lock is held\nif c.lockCh != nil {\n    return nil, errors.New(\"refusing to lock: client already holds a lock\")\n}","typeGuard":"// isLocked reports whether the consul RemoteClient currently holds a lock\nfunc isLocked(c *RemoteClient) bool {\n    if c == nil || c.lockCh == nil { return false }\n    select {\n    case <-c.lockCh:\n        return false // channel closed -> lock lost\n    default:\n        return true\n    }\n}","tryCatchPattern":null,"preventionTips":["Always pair Lock with a deferred Unlock in the same scope.","Never reuse a RemoteClient across independent operations that each lock.","Treat a second Lock on the same client as a programming bug."],"tags":["consul","remote-state","lock","state-machine","double-lock"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}