{"record":{"id":"5beb00ca8cf5ce62","repo":"puppetlabs/puppet","slug":"missing-operation-or","errorCode":null,"errorMessage":"Missing operation (-, =, or +)","messagePattern":"Missing operation \\(-, =, or \\+\\)","errorType":"validation","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/symbolic_file_mode.rb","lineNumber":95,"sourceCode":"    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 },\n          '-' => ->(m, v) { m & ~v },\n        }\n\n        dsl.split('').each do |op|\n          case op\n          when /[-+=]/\n            action = op\n            # Clear all bits, if this is assignment\n            value  = 0 if op == '='\n\n          when /[ugo]/\n            value = actions[action].call(value, snapshot_mode[op])\n\n          when /[rwx]/\n            value = actions[action].call(value, SymbolicMode[op])\n","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/symbolic_file_mode.rb#L77-L113","documentation":"Raised when the inner symbolic-mode loop tries to apply a permission while no -, +, or = operator has been seen yet — the internal action variable is still its '!' sentinel, whose lambda raises. Because the upstream clause regex requires an operator right after the who list, public inputs normally fail earlier with 'Missing action'; hitting this message usually means a malformed fragment reached the parser through a nonstandard path.","triggerScenarios":"A dsl fragment referencing permissions or who-letters before any operator character; in practice only reachable by calling symbolic_mode_to_int with hand-split or programmatically assembled clause fragments, since manifest input lacking an operator is caught by the 'Missing action' check first.","commonSituations":"Almost never seen from manifests; appears when wrapper code pre-splits or reassembles mode strings before calling the parser.","solutions":["Rewrite the mode in standard chmod syntax ('u=rw,g-w').","If hit from manifest input, simplify the mode string and retry; if it reproduces on current Puppet, file a bug with the exact mode value."],"exampleFix":"# before\nPuppet::Util::SymbolicMode.symbolic_mode_to_int('u=rw,g-w-ish')\n\n# after\nPuppet::Util::SymbolicMode.symbolic_mode_to_int('u=rw,g-w')","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'invalid mode' unless mode =~ /^([ugoa]*)([-+=][ugo rwxXst]*)+(,[ugoa]*[-+=][ugo rwxXst]*)*$/\nPuppet::Util::SymbolicMode.symbolic_mode_to_int(mode)","typeGuard":null,"tryCatchPattern":"begin\n  int_mode = Puppet::Util::SymbolicMode.symbolic_mode_to_int(mode, File.stat(path).mode)\nrescue Puppet::Error => e\n  raise \"bad symbolic mode #{mode.inspect} for #{path}: #{e.message}\"\nend","preventionTips":["Use canonical chmod syntax only.","Test generated mode strings against the parser in rspec.","Treat a hit as a parser bug candidate and minimize the input."],"tags":["puppet","file-mode","symbolic-mode","parser","internal-error"],"backgroundTag":"invalid-symbolic-mode","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}