{"record":{"id":"2ab2965cb200c54e","repo":"puppetlabs/puppet","slug":"missing-action","errorCode":null,"errorMessage":"Missing action","messagePattern":"Missing action","errorType":"validation","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/symbolic_file_mode.rb","lineNumber":79,"sourceCode":"\n    original_mode = {\n      's' => (to_mode & 0o7000) >> 9,\n      'u' => (to_mode & 0o0700) >> 6,\n      'g' => (to_mode & 0o0070) >> 3,\n      'o' => (to_mode & 0o0007) >> 0,\n      # Are there any execute bits set in the original mode?\n      'any x?' => (to_mode & 0o0111) != 0\n    }\n    final_mode = {\n      's' => original_mode['s'],\n      'u' => original_mode['u'],\n      'g' => original_mode['g'],\n      'o' => original_mode['o'],\n    }\n\n    modification.split(/\\s*,\\s*/).each do |part|\n      _, to, dsl = /^([ugoa]*)([-+=].*)$/.match(part).to_a\n      if dsl.nil? then raise Puppet::Error, _('Missing action') end\n\n      to = \"a\" unless to and to.length > 0\n\n      # We want a snapshot of the mode before we start messing with it to\n      # make actions like 'a-g' atomic.  Various parts of the DSL refer to\n      # the original mode, the final mode, or the current snapshot of the\n      # mode, for added fun.\n      snapshot_mode = {}\n      final_mode.each { |k, v| snapshot_mode[k] = v }\n\n      to.gsub('a', 'ugo').split('').uniq.each do |who|\n        value = snapshot_mode[who]\n\n        action = '!'\n        actions = {\n          '!' => ->(_, _) { raise Puppet::Error, _('Missing operation (-, =, or +)') },\n          '=' => ->(m, v) { m | v },\n          '+' => ->(m, v) { m | v },","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/symbolic_file_mode.rb#L61-L97","documentation":"Raised while parsing a symbolic mode clause: the comma-separated part did not match ^([ugoa]*)([-+=].*)$, so no who-plus-action pair exists. There is a who list (or bare permissions) but no operator to say what to do with them.","triggerScenarios":"symbolic_mode_to_int('rx'), mode => 'u', or any clause like 'ugo', 'rwx', or 'x' with no -, + or =; also fragments left behind by stray commas such as 'u+r,g'.","commonSituations":"Hand-written modes missing the operator ('rw' instead of 'u+rw'); trailing commas splitting the mode into an empty/bare fragment; concatenating variables into mode strings.","solutions":["Add the operator: 'u+rw', 'g=r-x', 'o-rwx'.","Remove extra/trailing commas and check that every comma-separated clause has who+operator+permissions."],"exampleFix":"# before\nfile { '/tmp/x': mode => 'rw' }\n\n# after\nfile { '/tmp/x': mode => 'u+rw' }","handlingStrategy":"validation","validationCode":"mode.split(/\\s*,\\s*/).each do |clause|\n  raise ArgumentError, \"clause #{clause.inspect} lacks an operator\" unless clause =~ /^([ugoa]*)([-+=].*)$/ \nend\nPuppet::Util::SymbolicMode.symbolic_mode_to_int(mode)","typeGuard":"def valid_symbolic_mode?(m)\n  m.is_a?(String) && !m.empty? &&\n    m.split(/\\s*,\\s*/).all? { |c| c =~ /^([ugoa]*)([-+=].*)$/ }\nend","tryCatchPattern":null,"preventionTips":["Model modes after chmod(1) examples: who+what.","Validate mode strings in module specs.","Avoid building modes by string concatenation."],"tags":["puppet","file-mode","symbolic-mode","parser","validation"],"backgroundTag":"invalid-symbolic-mode","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}