{"record":{"id":"31901a45ad3a6c79","repo":"basecamp/kamal","slug":"timed-out-waiting-for-deploy-lock","errorCode":null,"errorMessage":"Timed out waiting for deploy lock","messagePattern":"Timed out waiting for deploy lock","errorType":"exception","errorClass":"Kamal::Cli::LockError","httpStatus":null,"severity":"error","filePath":"lib/kamal/cli/base.rb","lineNumber":178,"sourceCode":"          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\n        execute_lock_release\n\n        KAMAL.holding_lock = false\n      end\n\n      def raise_if_locked\n        yield\n      rescue LockHeldError\n        say \"Deploy lock already in place!\", :red","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/cli/base.rb#L160-L196","documentation":"When a locking Kamal command finds the deploy lock held by another automatic deploy and wait_when_locked is set, it retries acquisition until a deadline (now + timeout seconds), printing 'Retrying in Xs (Ys remaining)'. If the deadline passes while the lock is still held, Kamal prints 'Timed out after Ns waiting for the deploy lock' and raises Kamal::Cli::LockError with 'Timed out waiting for deploy lock'.","triggerScenarios":"Two concurrent deploys (e.g. CI job plus a manual deploy): the second waits; if the first deploy runs longer than the configured wait timeout (wait_when_locked.timeout, default 30s in the lock config), the waiting deploy raises this error. Also triggered by a stale automatic lock left behind by a crashed deploy, since the status does contain the automatic message so Kamal keeps waiting until the deadline.","commonSituations":"Overlapping CI pipelines deploying the same environment; a long-running first deploy (slow image build/pull) exceeding the waiter's timeout; a previous deploy killed mid-run leaving the lock directory on the primary host.","solutions":["Check lock status: `kamal lock status` — see which deploy holds it and whether it is still genuinely running.","If the holder is dead/stale, release with `kamal lock release` (or wait for the active deploy to finish and simply re-run).","Increase the wait timeout in config/deploy.yml under lock: wait_when_locked: timeout: 300 (and interval) if concurrent deploys are normal for you.","Serialize deploys in CI (mutex/job concurrency limits) so they queue instead of racing."],"exampleFix":"# before (config/deploy.yml)\nlock:\n  wait_when_locked:\n    timeout: 30\n# after: wait long enough for overlapping deploys to finish\nlock:\n  wait_when_locked:\n    interval: 5\n    timeout: 600","handlingStrategy":"retry","validationCode":"loop do\n  status = `kamal lock status 2>/dev/null`\n  break if status.empty? || !status.include?(\"Locked\") # adapt to output\n  abort \"deploy lock busy for too long\" if Time.now > @deadline\n  sleep 10\nend\nsystem(\"kamal deploy\")","typeGuard":null,"tryCatchPattern":"retries = 0\nbegin\n  Kamal::CLI::Deploy.new.perform\nrescue Kamal::Cli::LockError => e\n  raise unless e.message.include?(\"Timed out\") && (retries += 1) <= 3\n  sleep 60 # let the other deploy finish, then retry\n  retry\nend","preventionTips":["Set lock: wait_when_locked timeout to comfortably exceed your longest deploy.","Serialize deploys in CI (concurrency groups) so waits are rare and short.","After any cancelled deploy, verify `kamal lock status` is clean before the next scheduled run."],"tags":["kamal","deploy-lock","timeout","concurrency","cli"],"backgroundTag":"deploy-lock-held","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}