{"record":{"id":"f3801fc3041a33c4","repo":"puppetlabs/puppet","slug":"puppet-tasks-missing-implementation","errorCode":"puppet.tasks/missing-implementation","errorMessage":"Task metadata for task %{name} specifies missing implementation %{implementation}","messagePattern":"Task metadata for task %(.+?) specifies missing implementation %(.+?)","errorType":"validation","errorClass":"Puppet::Module::Task::InvalidTask","httpStatus":null,"severity":"error","filePath":"lib/puppet/module/task.rb","lineNumber":175,"sourceCode":"      basename = name.split('::')[1] || 'init'\n      # If 'implementations' is defined, it needs to mention at least one\n      # implementation, and everything it mentions must exist.\n      metadata ||= {}\n      if metadata.key?('implementations')\n        unless metadata['implementations'].is_a?(Array)\n          msg = _(\"Task metadata for task %{name} does not specify implementations as an array\" % { name: name })\n          raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')\n        end\n\n        implementations = metadata['implementations'].map do |impl|\n          unless impl['requirements'].is_a?(Array) || impl['requirements'].nil?\n            msg = _(\"Task metadata for task %{name} does not specify requirements as an array\" % { name: name })\n            raise InvalidMetadata.new(msg, 'puppet.tasks/invalid-metadata')\n          end\n          path = executables.find { |real_impl| File.basename(real_impl) == impl['name'] }\n          unless path\n            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","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/module/task.rb#L157-L193","documentation":"Raised as Puppet::Error while parsing device.conf ($confdir/device.conf) when a second `[device.name]` section header appears for a name already registered. The parser tracks each stanza's starting line, so the message points at both the duplicate's line and the first occurrence's line. Reading happens for `puppet device` runs; a duplicate makes the configuration ambiguous, so parsing aborts.","triggerScenarios":"Editing device.conf and copy-pasting a stanza, leaving two [sw01.example.com] headers; merges or templating that append a stanza without removing the original; accidental duplication when managing device.conf with concat/file_line fragments.","commonSituations":"device.conf managed by multiple fragments or by both a template and a manual edit; renaming devices and forgetting to delete the old stanza; version-control merge conflicts resolved by keeping both blocks.","solutions":["Open device.conf at the line in %{file_error_location} and delete the duplicate stanza (the first is at %{device_error_location})","If device.conf is assembled from fragments, deduplicate the source of the fragment that emits the device twice","Validate before deploying: `awk '/^\\[/{print}' device.conf | sort | uniq -d` must be empty"],"exampleFix":"# before (/etc/puppetlabs/puppet/device.conf)\n[sw01.example.com]\ntype cisco\nurl ssh://admin:pass@sw01.example.com\n[sw01.example.com]\ntype cisco\nurl ssh://admin:pass@sw01.example.com\n\n# after\n[sw01.example.com]\ntype cisco\nurl ssh://admin:pass@sw01.example.com","handlingStrategy":"validation","validationCode":"names = File.read('/etc/puppetlabs/puppet/device.conf').scan(/^\\[([\\w.-]+)\\]/).flatten\ndups = names.tally.select { |_, c| c > 1 }.keys\nraise \"duplicate devices in device.conf: #{dups.join(', ')}\" unless dups.empty?","typeGuard":null,"tryCatchPattern":"begin\n  config.read\nrescue Puppet::Error => e\n  raise unless e.message =~ /Duplicate device/\n  # message names both line numbers; fix file and re-read\n  raise\nend","preventionTips":["Generate device.conf from a single source of truth (template over your inventory) instead of fragment edits","Add a uniq -d header check to config deployment pipelines","Resolve VCS merge conflicts on device.conf by inspection, never keep both sides"],"tags":["puppet","network-device","device-conf","duplicate-section","config-parsing"],"backgroundTag":"duplicate-config-section","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}