{"record":{"id":"5a3e33f50d358788","repo":"puppetlabs/puppet","slug":"there-is-more-than-one-basename-script-in-d","errorCode":null,"errorMessage":"There is more than one '%{basename}' script in %{dir}","messagePattern":"There is more than one '%(.+?)' script in %(.+?)","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/trusted_external.rb","lineNumber":29,"sourceCode":"    if setting_type == :file\n      return fetch_data(command, certname)\n    end\n\n    # command is a directory. Thus, data is a hash of <basename> => <data> for\n    # each executable file in command. For example, if the files 'servicenow.rb',\n    # 'unicorn.sh' are in command, then data is the following hash:\n    #   { 'servicenow' => <servicenow.rb output>, 'unicorn' => <unicorn.sh output> }\n    data = {}\n    Puppet::FileSystem.children(command).each do |file|\n      abs_path = Puppet::FileSystem.expand_path(file)\n      executable_file = Puppet::FileSystem.file?(abs_path) && Puppet::FileSystem.executable?(abs_path)\n      unless executable_file\n        Puppet.debug { _(\"Skipping non-executable file %{file}\") % { file: abs_path } }\n        next\n      end\n      basename = file.basename(file.extname).to_s\n      unless data[basename].nil?\n        raise Puppet::Error, _(\"There is more than one '%{basename}' script in %{dir}\") % { basename: basename, dir: command }\n      end\n\n      data[basename] = fetch_data(abs_path, certname)\n    end\n    data\n  end\n  module_function :retrieve\n\n  def fetch_data(command, certname)\n    result = Puppet::Util::Execution.execute([command, certname], {\n                                               :combine => false,\n                                               :failonfail => true,\n                                             })\n    JSON.parse(result)\n  end\n  module_function :fetch_data\nend\n","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/trusted_external.rb#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["List the directory and remove or rename the duplicate so each basename is unique: ls -l <dir>, then delete the stale script","Strip the executable bit from backups (chmod -x custom.rb.bak) — non-executable files are skipped with a debug message","If both scripts are legitimate, rename one so the fact keys differ (servicenow_v2.sh)","Verify recovery with puppet facts --debug, checking 'Retrieving trusted external data' completes"],"exampleFix":"# before (/etc/puppet/trusted-external contains)\n#   servicenow.rb   (executable)\n#   servicenow.sh   (executable)  => Puppet::Error\n\n# after\nrm /etc/puppet/trusted-external/servicenow.sh   # or: chmod -x servicenow.sh","handlingStrategy":"validation","validationCode":"require 'set'\ndir = Puppet[:trusted_external_command]\nseen = {}\nDir.children(dir).each do |f|\n  next unless File.executable?(File.join(dir, f))\n  base = File.basename(f, File.extname(f))\n  seen[base] ||= 0\n  seen[base] += 1\nend\ndupes = seen.select { |_, c| c > 1 }.keys\nraise \"duplicate trusted-external basenames: #{dupes.join(', ')}\" unless dupes.empty?","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deploy trusted-facts scripts from one source (package or puppet) so stale copies never linger","chmod -x backups; non-executable files are skipped by design","Name scripts uniquely per fact key (servicenow.rb vs servicenow_v2.sh)"],"tags":["puppet","trusted-facts","trusted-external","duplicate-key","config"],"backgroundTag":"duplicate-config-key","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}