{"record":{"id":"0d4f8a18e9f84062","repo":"puppetlabs/puppet","slug":"cannot-assign-to-a-numeric-match-result-variable","errorCode":null,"errorMessage":"Cannot assign to a numeric match result variable '$%{name}'","messagePattern":"Cannot assign to a numeric match result variable '\\$%(.+?)'","errorType":"exception","errorClass":"Puppet::ParseError","httpStatus":null,"severity":"error","filePath":"lib/puppet/parser/scope.rb","lineNumber":776,"sourceCode":"    end\n  end\n  private :transform_setting\n\n  VARNAME_TRUSTED = 'trusted'\n  VARNAME_FACTS = 'facts'\n  VARNAME_SERVER_FACTS = 'server_facts'\n  RESERVED_VARIABLE_NAMES = [VARNAME_TRUSTED, VARNAME_FACTS].freeze\n  TYPENAME_CLASS = 'Class'\n  TYPENAME_NODE = 'Node'\n\n  # Set a variable in the current scope.  This will override settings\n  # in scopes above, but will not allow variables in the current scope\n  # to be reassigned.\n  #   It's preferred that you use self[]= instead of this; only use this\n  # when you need to set options.\n  def setvar(name, value, options = EMPTY_HASH)\n    if name =~ /^[0-9]+$/\n      raise Puppet::ParseError, _(\"Cannot assign to a numeric match result variable '$%{name}'\") % { name: name } # unless options[:ephemeral]\n    end\n    unless name.is_a? String\n      raise Puppet::ParseError, _(\"Scope variable name %{name} is a %{class_type}, not a string\") % { name: name.inspect, class_type: name.class }\n    end\n\n    # Check for reserved variable names\n    if (name == VARNAME_TRUSTED || name == VARNAME_FACTS) && !options[:privileged]\n      raise Puppet::ParseError, _(\"Attempt to assign to a reserved variable name: '%{name}'\") % { name: name }\n    end\n\n    # Check for server_facts reserved variable name\n    if name == VARNAME_SERVER_FACTS && !options[:privileged]\n      raise Puppet::ParseError, _(\"Attempt to assign to a reserved variable name: '%{name}'\") % { name: name }\n    end\n\n    table = effective_symtable(options[:ephemeral])\n    if table.bound?(name)\n      error = Puppet::ParseError.new(_(\"Cannot reassign variable '$%{name}'\") % { name: name })","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/parser/scope.rb#L758-L794","documentation":"Raised by Scope#setvar when the variable name consists solely of digits (matches /^[0-9]+$/). Numeric variable names $0..$n are reserved for regex match captures set by MatchScope; they are populated automatically when a =~ or !~ expression with capture groups runs, and Puppet forbids explicit assignment to them. Note this guard lives in setvar, so it applies to DSL assignments and Ruby functions alike.","triggerScenarios":"Manifest code `$1 = 'foo'` or `$0 = generate_title()`; Ruby code calling `scope.setvar('2', 'x')` or `scope['3'] = value` on a parser scope. Also a function looping over user-supplied names that happen to be numeric strings.","commonSituations":"Porting templates/scripts that used numbered variables as throwaway names; Ruby functions auto-creating variables from external data (ERB, YAML keys like '1') without sanitizing names; confusing numeric match variables with regular variables in examples copied from regex-heavy manifests.","solutions":["Rename the variable to a non-numeric name: `$match1` instead of `$1`.","If you want regex captures, trigger them with a match expression instead of assigning: `$content =~ /(a+)/` then read $1.","In Ruby functions, reject or prefix numeric names before setvar: `name = \"v#{name}\" if name =~ /^[0-9]+$/`."],"exampleFix":"# before\n$1 = 'primary'      # Cannot assign to a numeric match result variable '$1'\n\n# after\n$primary = 'primary'\n# or set captures via an actual match:\n'primary' =~ /^(.+)$/   # now $1 == 'primary'","handlingStrategy":"validation","validationCode":"# Manifests: never use numeric names; if generating names programmatically:\n#   name = \"v#{raw}\" if raw =~ /^[0-9]+$/\n# Ruby guard before assignment:\nraise ArgumentError, 'numeric var' if name =~ /^[0-9]+$/\nscope.setvar(name, value) unless name =~ /^[0-9]+$/","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reserve $0..$n exclusively for regex captures; use named captures (`$<name>`-style facts) or named variables otherwise.","Sanitize external keys before creating scope variables from them.","Read $0 only after a successful =~ match in the same scope."],"tags":["puppet","variables","regex","reserved-names","scope"],"backgroundTag":"reserved-variable-assignment","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}