puppetlabs/puppet · error · InvalidName

Task names must start with a lowercase letter and be compose

Error message

Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores

What it means

Raised by Puppet::Module::Task#initialize when a task name fails Puppet::Module::Task.is_task_name?. Task names become the middle segment of the fully-qualified task name (module::taskname) and must start with a lowercase letter and contain only lowercase letters, digits, and underscores. Names are derived from filenames in the module's tasks/ directory, so an invalid filename makes every task in that module unlistable.

Source

Thrown at lib/puppet/module/task.rb:230

    def self.tasks_in_module(pup_module)
      task_files = Dir.glob(File.join(pup_module.tasks_directory, '*'))
                      .keep_if { |f| is_tasks_file?(f) }

      module_executables = task_files.reject(&method(:is_tasks_metadata_filename?)).map.to_a

      tasks = task_files.group_by { |f| task_name_from_path(f) }

      tasks.map do |task, executables|
        new_with_files(pup_module, task, executables, module_executables)
      end
    end

    attr_reader :name, :module, :metadata_file

    # file paths must be relative to the modules task directory
    def initialize(pup_module, task_name, module_executables, metadata_file = nil)
      unless Puppet::Module::Task.is_task_name?(task_name)
        raise InvalidName, _("Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores")
      end

      name = task_name == "init" ? pup_module.name : "#{pup_module.name}::#{task_name}"

      @module = pup_module
      @name = name
      @metadata_file = metadata_file
      @module_executables = module_executables || []
    end

    def self.read_metadata(file)
      if file
        content = Puppet::FileSystem.read(file, :encoding => 'utf-8')
        content.empty? ? {} : Puppet::Util::Json.load(content)
      end
    rescue SystemCallError, IOError => err
      msg = _("Error reading metadata: %{message}" % { message: err.message })
      raise InvalidMetadata.new(msg, 'puppet.tasks/unreadable-metadata')

View on GitHub (pinned to e227c27540)

Solutions

  1. Rename the offending file(s) in the module's tasks/ directory to /^[a-z][a-z0-9_]*$/ (e.g. 'restart_service.sh' -> 'restart_service.sh' kept, 'Restart-Service.sh' -> 'restart_service.sh').
  2. Delete stray non-task files (backups, swap files, .DS_Store) from tasks/.
  3. If you need word separation, use underscores, never dashes.
  4. Verify with `puppet module tasks list <module>` or `bolt task show`.

Example fix

# before: modules/mymodule/tasks/Restart-Service.py

# after: modules/mymodule/tasks/restart_service.py
Defensive patterns

Strategy: validation

Validate before calling

task_name = File.basename(file, File.extname(file))
unless task_name.match?(\A[a-z][a-z0-9_]*\z)
  warn "skipping invalid task file #{file}"; next
end

Type guard

def task_name?(str)
  str.is_a?(String) && str.match?(\A[a-z][a-z0-9_]*\z)
end

Try / catch

begin
  Puppet::Module::Task.new_with_files(mod, name, executables)
rescue Puppet::Module::Task::InvalidName => e
  warn "invalid task name #{name}: #{e.message}"
end

Prevention

When it happens

Trigger: A file in tasks/ such as 'MyTask.sh', '2restart.rb', 'my-task.py', or 'task.name' — the basename before the first extension is passed to is_task_name? when Task.instances or Task.new is constructed; the first bad file raises Puppet::Module::Task::InvalidName and aborts task discovery for the run.

Common situations: Copying a bolt plan/script with dashes into tasks/; Windows-origin files with uppercase names; test fixtures or editor swap files (e.g. '#task.rb#') left in tasks/; subdirectories with unsupported names.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/e5d6466c3f58fe78. Report an issue: GitHub.