{"record":{"id":"ebed7e0035b28836","repo":"puppetlabs/puppet","slug":"given-multiple-routes-with-identical-path-regexes","errorCode":null,"errorMessage":"Given multiple routes with identical path regexes: %{regexes}","messagePattern":"Given multiple routes with identical path regexes: %(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/network/http/handler.rb","lineNumber":24,"sourceCode":"require_relative '../../../puppet/network/http'\nrequire_relative '../../../puppet/util/profiler'\nrequire_relative '../../../puppet/util/profiler/aggregate'\nrequire 'resolv'\n\nmodule Puppet::Network::HTTP::Handler\n  include Puppet::Network::HTTP::Issues\n\n  # These shouldn't be allowed to be set by clients\n  # in the query string, for security reasons.\n  DISALLOWED_KEYS = %w[node ip]\n\n  def register(routes)\n    # There's got to be a simpler way to do this, right?\n    dupes = {}\n    routes.each { |r| dupes[r.path_matcher] = (dupes[r.path_matcher] || 0) + 1 }\n    dupes = dupes.filter_map { |pm, count| pm if count > 1 }\n    if dupes.count > 0\n      raise ArgumentError, _(\"Given multiple routes with identical path regexes: %{regexes}\") % { regexes: dupes.map(&:inspect).join(', ') }\n    end\n\n    @routes = routes\n    Puppet.debug(\"Routes Registered:\")\n    @routes.each do |route|\n      Puppet.debug(route.inspect)\n    end\n  end\n\n  # Retrieve all headers from the http request, as a hash with the header names\n  # (lower-cased) as the keys\n  def headers(request)\n    raise NotImplementedError\n  end\n\n  # The mime type is always passed to the `set_content_type` method, so\n  # it is no longer necessary to retrieve the Format's mime type.\n  #","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/network/http/handler.rb#L6-L42","documentation":"Puppet::Network::HTTP::Handler#register installs the route table for a server. It counts path_matcher occurrences across the given routes and raises ArgumentError listing the duplicated regexes if two Route objects share an identical path regex. Duplicate regexes would make routing order-dependent, so registration fails outright at server startup.","triggerScenarios":"Calling register with two Puppet::Network::HTTP::Route objects whose path Regex is equal: mounting the same v3 route set twice, concatenating route arrays that both contain the same entry, or custom handlers that compile to the same Regexp.","commonSituations":"Custom embedded Puppet masters assembling route lists from multiple sources; double registration during config reload or hot-restart; copy-pasted rack config.ru setup that runs twice.","solutions":["Deduplicate routes on path_matcher before registering: handler.register(routes.uniq(&:path_matcher))","Fix setup code so routes are registered exactly once (guard against double initialization on reload)","Give custom mounts genuinely distinct path regexes","Log each route's path_matcher at startup to spot the collision named in the error message"],"exampleFix":"# before: same route present in both lists\nhandler.register(routes + extra_routes)\n\n# after: deduplicate on the path regex\nhandler.register((routes + extra_routes).uniq(&:path_matcher))","handlingStrategy":"validation","validationCode":"dupes = routes.group_by(&:path_matcher).select { |_, v| v.size > 1 }.keys\nraise ArgumentError, \"duplicate route regexes: #{dupes.inspect}\" unless dupes.empty?\nhandler.register(routes)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate by path regex whenever routes are assembled dynamically","Make registration idempotent: track whether register already ran for this handler"],"tags":["routing","server","duplicates","startup"],"backgroundTag":"duplicate-route-registration","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}