{"record":{"id":"2b31d12c152ae013","repo":"ruby-concurrency/concurrent-ruby","slug":"no-action-given","errorCode":null,"errorMessage":"no action given","messagePattern":"no action given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/agent.rb","lineNumber":511,"sourceCode":"\n      if @error_mode && !ERROR_MODES.include?(@error_mode)\n        raise ArgumentError.new('unrecognized error mode')\n      elsif @error_mode.nil?\n        @error_mode = @error_handler ? :continue : :fail\n      end\n\n      @error_handler ||= DEFAULT_ERROR_HANDLER\n      @validator     = opts.fetch(:validator, DEFAULT_VALIDATOR)\n      @current       = Concurrent::AtomicReference.new(initial)\n      @error         = Concurrent::AtomicReference.new(nil)\n      @caller        = Concurrent::ThreadLocalVar.new(nil)\n      @queue         = []\n\n      self.observers = Collection::CopyOnNotifyObserverSet.new\n    end\n\n    def enqueue_action_job(action, args, executor)\n      raise ArgumentError.new('no action given') unless action\n      job = Job.new(action, args, executor, @caller.value || Thread.current.object_id)\n      synchronize { ns_enqueue_job(job) }\n    end\n\n    def enqueue_await_job(latch)\n      synchronize do\n        if (index = ns_find_last_job_for_thread)\n          job = Job.new(AWAIT_ACTION, [latch], Concurrent.global_immediate_executor,\n                        Thread.current.object_id)\n          ns_enqueue_job(job, index+1)\n        else\n          latch.count_down\n          true\n        end\n      end\n    end\n\n    def ns_enqueue_job(job, index = nil)","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/agent.rb#L493-L529","documentation":"Every Agent action is dispatched through enqueue_action_job, which requires a non-nil action - a proc/lambda/method object, or the block that Agent#send converts for you. A nil action means there is nothing to execute, so it raises ArgumentError before the job is enqueued.","triggerScenarios":"agent.send(nil); agent.send(handlers[step]) where the lookup returned nil for an unknown step; dynamically built actions (strategy maps, 'do_#{step}'.to_sym lookups, conditional procs like cond ? proc {...} : nil) dispatched without a nil check.","commonSituations":"Actions built from a lookup table or strategy hash with missing keys; passing opts[:handler] that was never set; conditional action construction where one branch returns nil but dispatch happens unconditionally.","solutions":["Check the action before dispatching: agent.send(action) if action.","Fail loudly at lookup time: action = handlers.fetch(step) { raise KeyError, \"no handler for #{step}\" } instead of silently sending nil.","When there is no named action, use the block form: agent.send { |old_value| compute(old_value) }."],"exampleFix":"# before\nagent.send(handlers[step])   # handlers[step] is nil for unknown steps\n\n# after\naction = handlers.fetch(step) { raise KeyError, \"no handler for #{step}\" }\nagent.send(action)","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"no action for #{step.inspect}\" if action.nil?\nagent.send(action) if action","typeGuard":null,"tryCatchPattern":"begin\n  agent.send(action, *args)\nrescue ArgumentError => e\n  raise ConfigError, \"agent dispatch failed: #{e.message}\"\nend","preventionTips":["Use fetch with a raise when looking up actions from strategy maps - never send a possibly-nil lookup.","Prefer the block form agent.send { |old| ... } when no named action exists.","Validate handler tables at startup, not at dispatch time."],"tags":["ruby","concurrent-ruby","agent","nil-safety","argumenterror"],"backgroundTag":"nil-argument-passed","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}