puppetlabs/puppet · error · Puppet::Error

There is more than one '%{basename}' script in %{dir}

Error message

There is more than one '%{basename}' script in %{dir}

What it means

When trusted_external_command points to a directory, Puppet runs every executable file inside it and builds external trusted facts keyed by each file's basename without extension (servicenow.rb -> 'servicenow'). If two executable files share the same basename (servicenow.rb and servicenow.sh), the second one hits an occupied key and Puppet::Error 'There is more than one X script in DIR' aborts trusted-fact retrieval during node classification.

Source

Thrown at lib/puppet/trusted_external.rb:29

    if setting_type == :file
      return fetch_data(command, certname)
    end

    # command is a directory. Thus, data is a hash of <basename> => <data> for
    # each executable file in command. For example, if the files 'servicenow.rb',
    # 'unicorn.sh' are in command, then data is the following hash:
    #   { 'servicenow' => <servicenow.rb output>, 'unicorn' => <unicorn.sh output> }
    data = {}
    Puppet::FileSystem.children(command).each do |file|
      abs_path = Puppet::FileSystem.expand_path(file)
      executable_file = Puppet::FileSystem.file?(abs_path) && Puppet::FileSystem.executable?(abs_path)
      unless executable_file
        Puppet.debug { _("Skipping non-executable file %{file}") % { file: abs_path } }
        next
      end
      basename = file.basename(file.extname).to_s
      unless data[basename].nil?
        raise Puppet::Error, _("There is more than one '%{basename}' script in %{dir}") % { basename: basename, dir: command }
      end

      data[basename] = fetch_data(abs_path, certname)
    end
    data
  end
  module_function :retrieve

  def fetch_data(command, certname)
    result = Puppet::Util::Execution.execute([command, certname], {
                                               :combine => false,
                                               :failonfail => true,
                                             })
    JSON.parse(result)
  end
  module_function :fetch_data
end

View on GitHub (pinned to e227c27540)

Solutions

  1. List the directory and remove or rename the duplicate so each basename is unique: ls -l <dir>, then delete the stale script
  2. Strip the executable bit from backups (chmod -x custom.rb.bak) — non-executable files are skipped with a debug message
  3. If both scripts are legitimate, rename one so the fact keys differ (servicenow_v2.sh)
  4. Verify recovery with puppet facts --debug, checking 'Retrieving trusted external data' completes

Example fix

# before (/etc/puppet/trusted-external contains)
#   servicenow.rb   (executable)
#   servicenow.sh   (executable)  => Puppet::Error

# after
rm /etc/puppet/trusted-external/servicenow.sh   # or: chmod -x servicenow.sh
Defensive patterns

Strategy: validation

Validate before calling

require 'set'
dir = Puppet[:trusted_external_command]
seen = {}
Dir.children(dir).each do |f|
  next unless File.executable?(File.join(dir, f))
  base = File.basename(f, File.extname(f))
  seen[base] ||= 0
  seen[base] += 1
end
dupes = seen.select { |_, c| c > 1 }.keys
raise "duplicate trusted-external basenames: #{dupes.join(', ')}" unless dupes.empty?

Prevention

When it happens

Trigger: Puppet[:trusted_external_command] set to a directory that contains both custom.rb and custom.sh (or custom and custom.py). Only executable regular files participate; backups like custom.rb.bak are skipped only if not executable, but backup scripts that remain executable (custom.rb.orig chmod +x, or editor droppings saved with the executable bit) collide.

Common situations: Iterating on trusted-facts scripts and leaving old copies (foo.sh after rewriting to foo.rb); deploying scripts with a package manager that leaves .rpmnew/.dpkg-dist copies executable; operators adding a .bak copy for rollback while keeping the exec bit.

Related errors


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