{"record":{"id":"9cb50600b827e601","repo":"puppetlabs/puppet","slug":"puppet-tasks-multiple-implementations","errorCode":"puppet.tasks/multiple-implementations","errorMessage":"Multiple executables were found in directory %{directory} for task %{name}; define 'implementations' in metadata to differentiate between them","messagePattern":"Multiple executables were found in directory %(.+?) for task %(.+?); define 'implementations' in metadata to differentiate between them","errorType":"validation","errorClass":"Puppet::Module::Task::InvalidTask","httpStatus":null,"severity":"error","filePath":"lib/puppet/module/task.rb","lineNumber":192,"sourceCode":"            msg = _(\"Task metadata for task %{name} specifies missing implementation %{implementation}\" % { name: name, implementation: impl['name'] })\n            raise InvalidTask.new(msg, 'puppet.tasks/missing-implementation', { missing: [impl['name']] })\n          end\n          { \"name\" => impl['name'], \"path\" => path }\n        end\n        return implementations\n      end\n\n      # If implementations isn't defined, then we use executables matching the\n      # task name, and only one may exist.\n      implementations = executables.select { |impl| File.basename(impl, '.*') == basename }\n      if implementations.empty?\n        msg = _('No source besides task metadata was found in directory %{directory} for task %{name}') %\n              { name: name, directory: directory }\n        raise InvalidTask.new(msg, 'puppet.tasks/no-implementation')\n      elsif implementations.length > 1\n        msg = _(\"Multiple executables were found in directory %{directory} for task %{name}; define 'implementations' in metadata to differentiate between them\") %\n              { name: name, directory: implementations[0] }\n        raise InvalidTask.new(msg, 'puppet.tasks/multiple-implementations')\n      end\n\n      [{ \"name\" => File.basename(implementations.first), \"path\" => implementations.first }]\n    end\n    private_class_method :find_implementations\n\n    def self.find_files(name, directory, metadata, executables, envname = nil)\n      # PXP agent relies on 'impls' (which is the task file) being first if there is no metadata\n      find_implementations(name, directory, metadata, executables) + find_extra_files(metadata, envname)\n    end\n\n    def self.is_tasks_metadata_filename?(name)\n      is_tasks_filename?(name) && name.end_with?('.json')\n    end\n\n    def self.is_tasks_executable_filename?(name)\n      is_tasks_filename?(name) && !name.end_with?('.json')\n    end","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/module/task.rb#L174-L210","documentation":"Raised as Puppet::Error by Config#parse_directive when the `url` value from device.conf fails URI.parse with URI::InvalidURIError. Only strict URI syntax errors trigger it — the scheme, host, or escaping is malformed enough that Ruby's URI parser rejects the string. %{value} echoes the exact text after the `url` keyword.","triggerScenarios":"A device url with spaces or unescaped special characters, a missing scheme (`sw01` instead of `ssh://sw01`), an unencoded password containing characters like `@` or `#`, or stray quotes/semicolons after the value. The regex captures everything after `url `, so trailing junk ends up inside %{value}.","commonSituations":"Passwords with reserved characters pasted into the url line; copying telnet/ssh URLs from documentation that wrap or add markup; missing scheme after template refactor.","solutions":["Rewrite the url in full form with scheme and encoded credentials: `url https://user:%40pass@device.example.com:443`","URI-encode any reserved characters in the password/host portion (RFC 3986 percent-encoding)","Ensure the scheme matches the device type's expected transport (ssh/telnet/https per the type's documentation)"],"exampleFix":"# before\n[sw01.example.com]\ntype cisco\nurl ssh://admin:p@ss word@sw01.example.com\n\n# after\n[sw01.example.com]\ntype cisco\nurl ssh://admin:p%40ss%20word@sw01.example.com","handlingStrategy":"validation","validationCode":"require 'uri'\nurl = 'ssh://admin:p%40ss@sw01.example.com'\nURI.parse(url) # raises URI::InvalidURIError here instead of inside Puppet\nputs 'url ok'","typeGuard":null,"tryCatchPattern":"begin\n  config.read\nrescue Puppet::Error => e\n  raise unless e.message =~ /is an invalid url/\n  # re-check the url line yourself: URI.parse(value) to get precise offset\n  raise\nend","preventionTips":["Percent-encode reserved characters (@ : / # space) in url passwords","Always include the transport scheme matching the device type","Run device urls through URI.parse in CI when they come from data files"],"tags":["puppet","network-device","device-conf","url-parsing","uri"],"backgroundTag":"invalid-url","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}