{"record":{"id":"efed7fbd8cf5131c","repo":"ruby-concurrency/concurrent-ruby","slug":"bad-options-options","errorCode":null,"errorMessage":"bad options #{options}","messagePattern":"bad options #(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb","lineNumber":815,"sourceCode":"        #                 drain signals including the Monitor\n        reference              = Reference.new\n        @Monitoring[reference] = pid\n        if pid.terminated.resolved?\n          # always return no-proc when terminated\n          tell DownSignal.new(pid, reference, NoActor.new(pid))\n        else\n          # otherwise let it race\n          pid.tell Monitor.new(@Pid, reference)\n          # no race, it cannot get anything else than NoActor\n          tell DownSignal.new(pid, reference, NoActor.new(pid)) if pid.terminated.resolved?\n        end\n        reference\n      end\n\n      def demonitor(reference, *options)\n        info  = options.delete :info\n        flush = options.delete :flush\n        raise ArgumentError, \"bad options #{options}\" unless options.empty?\n\n        pid          = @Monitoring.delete reference\n        demonitoring = !!pid\n        pid.tell DeMonitor.new @Pid, reference if demonitoring\n\n        if flush\n          # remove (one) down message having reference from mailbox\n          flushed = demonitoring ? !!@Mailbox.try_pop_matching(And[DownSignal, -> m { m.reference == reference }]) : false\n          return info ? !flushed : true\n        end\n\n        if info\n          return false unless demonitoring\n\n          if @Mailbox.peek_matching(And[DownSignal, -> m { m.reference == reference }])\n            @MonitoringLateDelivery[reference] = pid # allow to deliver the message once\n            return false\n          end","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby-edge/concurrent/edge/erlang_actor.rb#L797-L833","documentation":"`demonitor(reference, *options)` on an ErlangActor Environment takes its options as bare positional symbols; only `:info` and `:flush` are recognized, removed from the options array via `Array#delete`. After stripping those two symbols, anything left triggers `bad options #{options}` — and the message prints the leftovers verbatim. A keyword-style Hash is the classic trap: `options.delete :info` removes the symbol `:info`, never the element `{info: true}`, so the whole hash survives and is reported.","triggerScenarios":"`env.demonitor(ref, info: true)` -> raises with `bad options {:info=>true}`. `env.demonitor(ref, :info, :async)` -> `:async` is unknown. Correct calls: `env.demonitor(ref)`, `env.demonitor(ref, :info)`, `env.demonitor(ref, :info, :flush)`.","commonSituations":"Muscle memory from keyword-argument Ruby APIs; porting Erlang's `erlang:demonitor(Ref, [{flush, true}])` options literally as `flush: true`; copy-pasting between monitor/demonitor wrappers where the flag vocabulary drifted.","solutions":["Pass supported flags as plain symbols: `env.demonitor(ref, :info, :flush)`.","Never use `key: value` syntax — this API is symbol-flags only.","Read the leftover options in the message text to identify exactly which argument was rejected, then remove it.","Centralize demonitor calls in one helper that whitelists `[:info, :flush]`."],"exampleFix":"// before\nenv.demonitor(ref, info: true, flush: true) # bad options {:info=>true, :flush=>true}\n\n// after\nenv.demonitor(ref, :info, :flush)","handlingStrategy":"validation","validationCode":"ALLOWED_DEMONITOR_FLAGS = %i[info flush].freeze\n\ndef demonitor!(env, ref, *flags)\n  bad = flags - ALLOWED_DEMONITOR_FLAGS\n  raise ArgumentError, \"unsupported flags: #{bad.inspect}\" unless bad.empty?\n  env.demonitor(ref, *flags)\nend","typeGuard":null,"tryCatchPattern":"begin\n  env.demonitor(ref, *flags)\nrescue ArgumentError => e\n  raise unless e.message.start_with?('bad options')\n  env.demonitor(ref) # fall back to no flags\nend","preventionTips":["This API takes bare symbols only: :info, :flush.","Never pass key: value hashes — Array#delete :info cannot remove them.","Read the leftover options printed in the message to find the culprit argument."],"tags":["concurrent-ruby","concurrent-ruby-edge","erlang-actor","demonitor","argumenterror","options"],"backgroundTag":"unrecognized-option-passed","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}