hashicorp/vagrant · error · Vagrant::Plugin::V2::InvalidCommandName

Commands can only contain letters, numbers, and hyphens

Error message

Commands can only contain letters, numbers, and hyphens

What it means

Plugin DSL validation in the v2 API: the name passed to `command(name, **opts)` must match /^[-a-z0-9]+$/i — letters, digits, and hyphens only (lib/vagrant/plugin/v2/plugin.rb:93). Anything else (underscores, colons, spaces) raises Vagrant::Plugin::V2::Errors::InvalidCommandName while the plugin is being defined, before the command is registered or can run.

Source

Thrown at lib/vagrant/plugin/v2/plugin.rb:93

        #   set, every middleware action is hooked.
        # @return [Array] List of the hooks for the given action.
        def self.action_hook(name, hook_name=nil, &block)
          # The name is currently not used but we want it for the future.
          hook_name = hook_name.to_s if hook_name

          hook_name ||= ALL_ACTIONS
          components.action_hooks[hook_name.to_sym] << block
        end

        # Defines additional command line commands available by key. The key
        # becomes the subcommand, so if you register a command "foo" then
        # "vagrant foo" becomes available.
        #
        # @param [String] name Subcommand key.
        def self.command(name, **opts, &block)
          # Validate the name of the command
          if name.to_s !~ /^[-a-z0-9]+$/i
            raise InvalidCommandName, "Commands can only contain letters, numbers, and hyphens"
          end

          # By default, the command is primary
          opts[:primary] = true if !opts.key?(:primary)

          # Register the command
          components.commands.register(name.to_sym) do
            [block, opts]
          end

          nil
        end

        # Defines additional communicators to be available. Communicators
        # should be returned by a block passed to this method. This is done
        # to ensure that the class is lazy loaded, so if your class inherits
        # from or uses any Vagrant internals specific to Vagrant 1.0, then
        # the plugin can still be defined without breaking anything in future

View on GitHub (pinned to 35f3160f4a)

Solutions

  1. Rename the subcommand to hyphenated lowercase: my_command -> my-command
  2. Only the subcommand key is validated — the gem/plugin display name can stay as-is
  3. Use hyphenated prefixes for grouping (myplug-list, myplug-add)

Example fix

# before
class MyPlugin < Vagrant.plugin("2")
  name "my plugin"
  command "my_command" do
    require File.expand_path("../command", __FILE__)
    Command
  end
end

# after
class MyPlugin < Vagrant.plugin("2")
  name "my plugin"
  command "my-command" do
    require File.expand_path("../command", __FILE__)
    Command
  end
end
Defensive patterns

Strategy: type-guard

Validate before calling

COMMAND_NAME_RE = /\A[-a-zA-Z0-9]+\z/
raise "invalid command name '#{name}'" unless name.to_s.match?(COMMAND_NAME_RE)

Type guard

# Ruby guard for the Vagrant v2 command DSL
COMMAND_NAME_RE = /\A[-a-zA-Z0-9]+\z/
def valid_command_name?(name)
  name.is_a?(String) && !name.empty? && name.match?(COMMAND_NAME_RE)
end

Try / catch

begin
  command "my-command" do
    require_relative "command"
    Command
  end
rescue Vagrant::Plugin::V2::Errors::InvalidCommandName => e
  warn e.message # "Commands can only contain letters, numbers, and hyphens"
end

Prevention

When it happens

Trigger: A plugin's `class MyPlugin < Vagrant.plugin("2")` block calling `command "my_command"` or `command "ns:cmd"` — components.commands.register is never reached because the name check fails first.

Common situations: Plugin authors carrying Ruby underscore naming or rake-style colon namespacing into subcommand keys; automated code generation that derives the command key from a class name containing `::`.

Related errors


AI-assisted analysis of hashicorp/vagrant@35f3160f4a (2026-08-21). Data as JSON: /api/errors/259bb0380311cd4d. Report an issue: GitHub.