{"record":{"id":"6944376a8e116fe5","repo":"travisjeffery/timecop","slug":"safe-mode-is-enabled-only-calls-passing-a-block-a","errorCode":null,"errorMessage":"Safe mode is enabled, only calls passing a block are allowed.","messagePattern":"Safe mode is enabled, only calls passing a block are allowed\\.","errorType":"exception","errorClass":"SafeModeException","httpStatus":null,"severity":"error","filePath":"lib/timecop/timecop.rb","lineNumber":214,"sourceCode":"  end\n\n  def initialize #:nodoc:\n    @stack = []\n    @safe = nil\n    @thread_safe = false\n  end\n\n  def thread_safe=(t)\n    initialize\n    @thread_safe = t\n  end\n\n  def thread_safe\n    @thread_safe\n  end\n\n  def travel(mock_type, *args, &block) #:nodoc:\n    raise SafeModeException if Timecop.safe_mode? && !block_given? && !@safe\n\n    stack_item = TimeStackItem.new(mock_type, *args)\n\n    stack_backup = stack.dup\n    stack << stack_item\n\n    if block_given?\n      safe_backup = @safe\n      @safe = true\n      begin\n        yield stack_item.time\n      ensure\n        stack.replace stack_backup\n        @safe = safe_backup\n      end\n    end\n  end\n","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/travisjeffery/timecop/blob/b3c9a5a6f06b7185c8012896b80d0a5b11df5d34/lib/timecop/timecop.rb#L196-L232","documentation":"Timecop::SafeModeException is raised when the timecop gem runs in safe mode and Timecop.freeze, Timecop.travel, or Timecop.scale is called WITHOUT a block. Safe mode (Timecop.safe_mode = true) is an opt-in policy that forces block-scoped time mocking so mocked time is always restored; the block path in Timecop#travel (lib/timecop/timecop.rb:221-230) pops the stack in an ensure clause. The guard at lib/timecop/timecop.rb:214 fires only when all three conditions hold: safe mode is on, no block is given, and the internal @safe flag is not set. @safe is set to true while a block is executing, so nested blockless calls made from inside a block are permitted.","triggerScenarios":"1) Timecop.safe_mode = true is active (spec_helper.rb, rails_helper.rb, or a support file) and any blockless call is made: Timecop.freeze, Timecop.freeze(Time.local(1990)), Timecop.travel(2008, 9, 1), Timecop.scale(3600) — freeze/travel/scale all route through send_travel -> instance.travel, so all three are guarded. 2) A before/after hook pair (Timecop.freeze(t) in before, Timecop.return in after — the README setup/teardown pattern) runs while safe mode is enabled. 3) A Rails config/environments/test.rb after_initialize hook calls Timecop.travel(t) blockless (the exact README recipe) while safe mode was switched on elsewhere. 4) A wrapper/helper method drops the caller's block by not accepting and forwarding &block, silently turning a legal call into a blockless one.","commonSituations":"Safe mode enabled in shared test bootstrap (spec/support, a copied boilerplate file, or a company test-tooling gem) by another team member, after which legacy blockless Timecop calls fail suite-wide. Refactoring a block call into a before/after pair without realizing safe mode forbids exactly that pattern. Adopting a shared gem or CI config that flips Timecop.safe_mode globally. Defining helper methods like def freeze_time(time); Timecop.freeze(time); end that never forward a block.","solutions":["Pass a block so time is restored automatically: Timecop.freeze(new_time) do ... end (same for travel and scale).","If you mock time across hooks, move the freeze into each example body inside a block; safe mode exists precisely to forbid the before/after blockless pattern.","If blockless usage is intentional (whole-suite mocked time), disable the policy: set Timecop.safe_mode = false or remove the Timecop.safe_mode = true line from your test bootstrap.","Locate where safe mode is enabled: grep -r 'safe_mode' spec/ test/ config/ lib/ and decide whether the policy or the call sites change.","When wrapping Timecop in helpers or around-hooks, accept and forward &block explicitly so nested calls keep the protection of the enclosing block (@safe is true only during block execution)."],"exampleFix":"// before\nTimecop.safe_mode = true\nTimecop.freeze(Time.local(1990)) # => Timecop::SafeModeException: Safe mode is enabled, only calls passing a block are allowed.\n\n// after\nTimecop.safe_mode = true\nTimecop.freeze(Time.local(1990)) do\n  # Time.now / Date.today / DateTime.now mocked only inside this block, restored on exit\n  assert invoice.overdue?\nend","handlingStrategy":"validation","validationCode":"# Guard before any blockless Timecop call\nif Timecop.safe_mode?\n  raise ArgumentError, \"Timecop is in safe mode: pass a block to freeze/travel/scale\"\nend\nTimecop.freeze(target_time)","typeGuard":"# Ruby predicate helpers for wrapper methods\ndef blockless_timecop_allowed?\n  !Timecop.safe_mode?\nend\n\ndef with_frozen_time(time, &block)\n  raise Timecop::SafeModeException if Timecop.safe_mode? && block.nil?\n  Timecop.freeze(time, &block)\nend","tryCatchPattern":"begin\n  Timecop.freeze(new_time)\nrescue Timecop::SafeModeException\n  # policy violation, not a transient failure: switch to block form or disable safe mode\n  Timecop.freeze(new_time) { yield }\nend\n# Rescue the specific class only; never blanket-rescue StandardError for this error.","preventionTips":["Always call freeze/travel/scale with a block: the ensure clause restores real time even when the block raises, keeping tests order-independent.","Centralize time mocking in one helper (e.g. a travel_to wrapper) so the safe-mode contract is enforced in a single place.","Comment or log where Timecop.safe_mode = true is set in your test bootstrap so teammates can grep it before adding blockless calls.","When wrapping Timecop in helpers or around-hooks, accept and forward &block explicitly — dropping it silently turns a legal nested call into a SafeModeException.","Pair any before/after freeze pattern with after { Timecop.return } so one failing spec cannot leak mocked time into the next (the exact leak safe mode protects against)."],"tags":["ruby","timecop","time-mocking","safe-mode","testing","block-required"],"backgroundTag":"safe-mode-enforcement","analyzedSha":"b3c9a5a6f06b7185c8012896b80d0a5b11df5d34","analyzedAt":"2026-08-23T11:14:46.097Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}