{"record":{"id":"7cdb308c1039012d","repo":"basecamp/kamal","slug":"deploy-lock-held-manually-not-waiting-run-kamal","errorCode":null,"errorMessage":"Deploy lock held manually, not waiting. Run 'kamal lock help' for more information","messagePattern":"Deploy lock held manually, not waiting\\. Run 'kamal lock help' for more information","errorType":"exception","errorClass":"Kamal::Cli::LockError","httpStatus":null,"severity":"error","filePath":"lib/kamal/cli/base.rb","lineNumber":169,"sourceCode":"        timeout = KAMAL.lock_wait_timeout\n        interval = KAMAL.lock_wait_interval\n        deadline = Time.now + timeout\n        details_shown = false\n\n        say \"Acquiring the deploy lock (waiting up to #{timeout}s)...\", :magenta\n\n        loop do\n          execute_lock_acquire(AUTOMATIC_DEPLOY_LOCK_MESSAGE)\n          break\n        rescue LockHeldError\n          unless details_shown\n            status = capture_lock_status\n\n            say \"Deploy lock is held by:\", :magenta\n            puts status\n\n            unless status.include?(AUTOMATIC_DEPLOY_LOCK_MESSAGE)\n              raise LockError, \"Deploy lock held manually, not waiting. Run 'kamal lock help' for more information\"\n            end\n\n            details_shown = true\n          end\n\n          remaining = (deadline - Time.now).to_i\n          if remaining <= 0\n            say \"Timed out after #{timeout}s waiting for the deploy lock\", :red\n            raise LockError, \"Timed out waiting for deploy lock\"\n          end\n\n          say \"Retrying in #{interval}s (#{remaining}s remaining)...\", :magenta\n          sleep [ interval, remaining ].min\n        end\n      end\n\n      def release_lock\n        say \"Releasing the deploy lock...\", :magenta","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/cli/base.rb#L151-L187","documentation":"Before mutating commands, Kamal acquires a deploy lock (a directory on the primary host) and retries in a loop while another automatic deploy holds it. The captured lock status always shows who holds it; if the message does not contain Kamal's AUTOMATIC_DEPLOY_LOCK_MESSAGE, the lock was created manually (via `kamal lock acquire`), which signals an intentional hold — so Kamal refuses to wait and raises Kamal::Cli::LockError telling you to run `kamal lock help`.","triggerScenarios":"Running any locking command (`kamal deploy`, `kamal app stop`, etc.) with a wait_when_locked timeout configured, while the primary host has a lock directory created by `kamal lock acquire 'reason'` (manual). The first acquire attempt raises LockHeldError, status is captured, and because the manual message differs from the automatic one, LockError is raised immediately.","commonSituations":"An operator manually locked servers for maintenance (frozen deploys) and forgot to release; a teammate ran `kamal lock acquire` during an incident; stale manual lock from a previous maintenance window never released with `kamal lock release`.","solutions":["Check who/why holds it: `kamal lock status` — the message shows the reason and version recorded at acquire time.","If the manual hold is no longer needed, release it: `kamal lock release`, then re-run your deploy.","If the hold is intentional, coordinate with the operator who acquired it before releasing.","Never delete the lock directory manually on the host unless `kamal lock release` fails; prefer the CLI so state stays consistent."],"exampleFix":"# before\nkamal deploy   # -> Deploy lock held manually, not waiting.\n# after\nkamal lock status   # see who/why it is held\nkamal lock release  # once maintenance is done\nkamal deploy","handlingStrategy":"try-catch","validationCode":"status = `kamal lock status`\nabort \"manual deploy lock held:\\n#{status}\" if status.include?(\"held\") # adapt to your lock message format\n# safe to deploy","typeGuard":"def manual_lock_held?(status_output)\n  status_output.include?(\"Locked\") && !status_output.include?(\"Automatic\")\nend","tryCatchPattern":"begin\n  Kamal::CLI::Deploy.new.perform\nrescue Kamal::Cli::LockError => e\n  if e.message.include?(\"held manually\")\n    `kamal lock status` # record holder, then notify operator instead of force-releasing\n  end\n  raise\nend","preventionTips":["Run `kamal lock status` at the top of deploy jobs and fail with context instead of mid-deploy.","Pair every manual `kamal lock acquire` with an automatic release step (trap/finally) in maintenance runbooks."],"tags":["kamal","deploy-lock","concurrency","cli"],"backgroundTag":"deploy-lock-held","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}