basecamp/kamal · error · ArgumentError

No command provided. You must specify a command to execute.

Error message

No command provided. You must specify a command to execute.

What it means

`kamal app exec CMD...` requires at least one positional argument: the command to run inside the app container. Thor collects variadic args, so invoking exec with no arguments parses fine, and Kamal then explicitly raises ArgumentError ('No command provided. You must specify a command to execute.') after the flag-compatibility checks and before connecting to any host.

Source

Thrown at lib/kamal/cli/app.rb:109

  desc "exec [CMD...]", "Execute a custom command on servers within the app container (use --help to show options)"
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
  option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
  option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
  option :raw, type: :boolean, default: false, desc: "Output raw, unmodified stdout"
  def exec(*cmd)
    raw = options[:raw]

    if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
      raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
    end

    if raw && (incompatible_options = [ :interactive, :detach ].select { |key| options[key] }.presence)
      raise ArgumentError, "Raw is not compatible with #{incompatible_options.join(" or ")}"
    end

    if cmd.empty?
      raise ArgumentError, "No command provided. You must specify a command to execute."
    end

    with_raw_output(raw) do
      pre_connect_if_required

      cmd = Kamal::Utils.join_commands(cmd)
      env = options[:env]
      detach = options[:detach]
      quiet = options[:quiet]
      case
      when options[:interactive] && options[:reuse]
        say "Get current version of running container...", :magenta unless options[:version]
        using_version(options[:version] || current_running_version) do |version|
          say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
          run_locally { exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_existing_container_over_ssh(cmd, env: env) }
        end

      when options[:interactive]

View on GitHub (pinned to eee0083b38)

Solutions

  1. Pass an explicit command: `kamal app exec --reuse 'ls -la /app'`.
  2. If the command comes from a variable, default or fail fast before calling kamal: `[ -n "$CMD" ] || { echo 'CMD empty'; exit 1; }`.
  3. Quote multi-word commands so they arrive as args rather than being globbed/split by the shell.

Example fix

# before
CMD="" kamal app exec --reuse "$CMD"
# after
CMD="rails db:migrate" kamal app exec --reuse $CMD
# or fail fast in the caller:
[ -n "$CMD" ] || { echo "CMD is empty" >&2; exit 1; }
kamal app exec --reuse $CMD
Defensive patterns

Strategy: validation

Validate before calling

cmd = ENV["APP_EXEC_CMD"]
abort "APP_EXEC_CMD is empty; refusing to run kamal app exec" if cmd.to_s.strip.empty?
system("kamal app exec --reuse #{cmd}")

Try / catch

begin
  Kamal::CLI::App.new.invoke(:exec, cmd_args)
rescue ArgumentError => e
  raise unless e.message.include?("No command provided")
  abort "build the command before calling exec"
end

Prevention

When it happens

Trigger: Running bare `kamal app exec`, or `kamal app exec --reuse` with only options and no positional command, or quoting mistakes that make the command disappear (e.g. an empty string variable: `kamal app exec "$CMD"` with CMD unset).

Common situations: Scripts building the command dynamically from an env var that is empty in CI; forgetting that unlike `kamal app exec -i bash`, some shells need the command quoted as one arg; running exec in a loop where one iteration has no command.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21). Data as JSON: /api/errors/3e9728fec76249e4. Report an issue: GitHub.