{"record":{"id":"f6a7e5c378286038","repo":"ruby-concurrency/concurrent-ruby","slug":"no-block-given-f6a7e5","errorCode":null,"errorMessage":"no block given","messagePattern":"no block given","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/concurrent-ruby/concurrent/tvar.rb","lineNumber":83,"sourceCode":"  # * If an exception escapes an atomically block it will abort the transaction.\n  #\n  # * It is undefined behaviour to use callcc or Fiber with atomically.\n  #\n  # * If you create a new thread within an atomically, it will not be part of\n  #     the transaction. Creating a thread counts as a side-effect.\n  #\n  # Transactions within transactions are flattened to a single transaction.\n  #\n  # @example\n  #   a = new TVar(100_000)\n  #   b = new TVar(100)\n  #\n  #   Concurrent::atomically do\n  #     a.value -= 10\n  #     b.value += 10\n  #   end\n  def atomically\n    raise ArgumentError.new('no block given') unless block_given?\n\n    # Get the current transaction\n\n    transaction = Transaction::current\n\n    # Are we not already in a transaction (not nested)?\n\n    if transaction.nil?\n      # New transaction\n\n      begin\n        # Retry loop\n\n        loop do\n\n          # Create a new transaction\n\n          transaction = Transaction.new","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/ruby-concurrency/concurrent-ruby/blob/0b88d5ff75f69b3740c8f0868e76f833cb2fd45d/lib/concurrent-ruby/concurrent/tvar.rb#L65-L101","documentation":"Concurrent.atomically (or Concurrent::TVar-based code) runs a software-transactional-memory transaction and requires a block describing the reads/writes of TVars. Calling it without a block raises ArgumentError('no block given') before any transaction starts. Nested calls are flattened into the outer transaction, so the block is the only way to express the transactional body.","triggerScenarios":"Concurrent.atomically with no block; forwarding a transaction body stored in a variable without &block; guard clauses that conditionally skip attaching the block (e.g. 'if in_transaction, run outside STM' implemented by dropping the block).","commonSituations":"Wrapping STM transactions in helper methods and losing the & forwarding; DSL builders that assemble transactions dynamically and end up with nil bodies; refactors from ivar mutation to TVar that leave call sites blockless.","solutions":["Pass the body as a block: Concurrent.atomically { account.value -= 10 }","When forwarding from a method, capture and re-attach: def transfer(&blk); Concurrent.atomically(&blk); end","Validate at your API boundary: raise ArgumentError, 'transaction body required' unless block_given?"],"exampleFix":"# before\ndef transfer(from, to, amount)\n  Concurrent.atomically # nothing to run\nend\n\n# after\ndef transfer(from, to, amount)\n  Concurrent.atomically do\n    from.value -= amount\n    to.value += amount\n  end\nend","handlingStrategy":"validation","validationCode":"def stm(&block)\n  raise ArgumentError, 'transaction body block required' unless block\n  Concurrent.atomically(&block)\nend","typeGuard":"def transaction_body?(obj)\n  obj.respond_to?(:call)\nend","tryCatchPattern":"begin\n  Concurrent.atomically { mutate_tvrs }\nrescue ArgumentError => e\n  raise unless e.message == 'no block given'\n  raise ConfigError, 'transaction body missing for transfer'\nend","preventionTips":["Forward blocks explicitly with &blk in STM wrapper methods","Fail fast in builders when the transaction proc is nil","Prefer Concurrent.atomically { ... } with a literal block at call sites"],"tags":["ruby","concurrency","stm","tvar","argument-error","missing-block"],"backgroundTag":"missing-block-argument","analyzedSha":"0b88d5ff75f69b3740c8f0868e76f833cb2fd45d","analyzedAt":"2026-08-21T20:12:56.291Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}