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
- Pass an explicit command: `kamal app exec --reuse 'ls -la /app'`.
- If the command comes from a variable, default or fail fast before calling kamal: `[ -n "$CMD" ] || { echo 'CMD empty'; exit 1; }`.
- 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
- Always pass a literal command string in scripts; fail fast when it comes from a variable.
- Prefer full, quoted commands (`'rails db:migrate'`) so shells cannot swallow them.
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
- Detach is not compatible with #{incompatible_options.join("
- Raw is not compatible with #{incompatible_options.join(" or
- Raw is not compatible with interactive
- Raw is not compatible with interactive
- Missing required option '--account'
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/3e9728fec76249e4.
Report an issue: GitHub.