{"record":{"id":"c268f5318e08bb6b","repo":"ruby-concurrency/concurrent-ruby","slug":"no-block-given-c268f5","errorCode":null,"errorMessage":"no block given","messagePattern":"no block given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/mvar.rb","lineNumber":124,"sourceCode":"\n        # If we timed out we won't be empty\n        if unlocked_empty?\n          @value = value\n          @full_condition.signal\n          apply_deref_options(value)\n        else\n          TIMEOUT\n        end\n      end\n    end\n\n    # Atomically `take`, yield the value to a block for transformation, and then\n    # `put` the transformed value. Returns the pre-transform value. A timeout can\n    # be set to limit the time spent blocked, in which case it returns `TIMEOUT`\n    # if the time is exceeded.\n    # @return [Object] the pre-transform value, or `TIMEOUT`\n    def modify(timeout = nil)\n      raise ArgumentError.new('no block given') unless block_given?\n\n      @mutex.synchronize do\n        wait_for_full(timeout)\n\n        # If we timed out we'll still be empty\n        if unlocked_full?\n          value = @value\n          @value = yield value\n          @full_condition.signal\n          apply_deref_options(value)\n        else\n          TIMEOUT\n        end\n      end\n    end\n\n    # Non-blocking version of `take`, that returns `EMPTY` instead of blocking.\n    def try_take!","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/mvar.rb#L106-L142","documentation":"MVar is a synchronized single-slot container (empty/full states). #modify atomically takes the current value, yields it to your block, and puts the transformed value back — the block is essential, so calling modify (with or without a timeout) without one raises ArgumentError 'no block given' before any locking happens.","triggerScenarios":"mvar.modify with no block; mvar.modify(5) where 5 was intended as a new value rather than the timeout; helper methods that accept a timeout and a block but drop the & forwarding.","commonSituations":"Expecting modify(value) to behave like put(value); wrapping MVar in repository-style APIs where the transformation lives in a stored proc; first contact with MVar's Haskell-style API where every operation is block-based.","solutions":["Always pass the transformation block: mvar.modify { |v| v + 1 }","Remember the only positional argument is the timeout: mvar.modify(2) { |v| v + 1 }","If the transformation is stored, pass it as a block: mvar.modify(&transform)"],"exampleFix":"# before\nmvar.modify(2) # intended 'set to 2 after 2s'\n\n# after\nmvar.put(2)                        # plain overwrite (blocks when full)\nmvar.modify(2) { |v| v * 10 }      # 2 = timeout, block = transform","handlingStrategy":"validation","validationCode":"raise ArgumentError, 'transform block required' unless block_given?\nmvar.modify(timeout) { |v| yield(v) }","typeGuard":"->(obj) { obj.respond_to?(:call) } # then mvar.modify(&obj)","tryCatchPattern":"begin\n  mvar.modify(timeout) { |v| transform.call(v) }\nrescue ArgumentError => e\n  raise ArgumentError, 'modify needs a transform block' if /no block/.match?(e.message)\n  raise\nend","preventionTips":["Remember modify(timeout = nil) { |v| ... } — the only positional arg is a timeout, not a value","Use put(value) for plain writes; modify is for read-transform-write","Check the MVar result for TIMEOUT after modify with a timeout instead of assuming success"],"tags":["ruby","concurrency","mvar","block-argument","argument-validation"],"backgroundTag":"missing-block-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}