runtimeanalyz.ing ● early access · sign-up open

← All capabilities

how_it_got_here

CLI · MCP free

How execution got here. The real call chains that reach a method, in order, taken from recorded runs. The stack trace you'd have got if it had raised, on demand, for every route in.

One call tree · route 3 of 3
SubscriptionsController root
call
1
Cancellation
Cancellation
process
2
Refund
Refund
void!
3
Invoice voided
Cancellation
void!
4 · same tree, again
Invoice voided twice
Other routes Admin::InvoicesController#destroy → Invoice#void! 29 calls Refund#process → Invoice#void! 9 calls
Every record knows its call tree and its place in it, so each call to void! walks back to its root. In this tree it's reached twice: once through Refund, and once straight from Cancellation.

The problem

A support ticket says an invoice was voided twice. Invoice#void! is meant to run once, from the admin screen, so how did it run again?

class Invoice
  def void!
    accounts.credit(total, memo: "void #{number}")
    update!(state: :void)
  end
end

A stack trace would answer it, but you only get one of those when something raises, and nothing did. So you read code: void! is called from Admin::InvoicesController, from Refund#process, and from a Cancellation service. Each of those has its own callers. Twenty minutes in you have a whiteboard of plausible routes and no idea which ones really happen.

The usual next step is puts caller in void!, a rerun, and a scroll through the log.

The fix

The recording already has the stack for every call to void! that ran:

$ ra how_it_got_here Invoice#void!

3 distinct routes · 41 calls

  Admin::InvoicesController#destroy                            29 calls
  └ Invoice#void!

  Refund#process                                                9 calls
  └ Invoice#void!

  SubscriptionsController#cancel                                3 calls
  └ Cancellation#call
    └ Refund#process
      └ Invoice#void!
    └ Invoice#void!

The third route is the bug. Cancellation#call refunds the last invoice, which voids it, and then voids it again itself. Every cancellation with a refund due does it. The fix is to leave voiding to Refund:

class Cancellation
  def call
    Refund.new(subscription.last_invoice).process
    subscription.update!(state: :cancelled)
  end
end

How it works

Each record in the recording carries the call tree it belongs to and its step within that tree, along with the method that made the call and the method that received it. Starting from every recorded call to Invoice#void!, how_it_got_here walks back up its own tree, one caller at a time, to the root.

Identical chains are merged and counted, so a route that ran a thousand times appears once, with its count, and the rare one isn’t buried under it. Each chain is in call order, the same as a stack trace read from the top.

When the recording came from your test suite, each tree is stamped with the example that produced it, so every route can also name a spec that takes it: --examples adds them.

Limits

  • Routes that ran. A path no recorded run took isn’t listed. Record your app as well as your suite and the production routes join the list.
  • App methods only. Frames inside gems and the framework aren’t recorded, so a chain starts at the first method of yours: the controller action, the job’s perform, the spec.
  • Routes, not values. It shows that Cancellation#call reached void! twice. It can’t show which invoice, or what state it was in.

what_calls

CLI · LSP · MCP free

Who can call this method. Every caller of a method from real runs, with where and how often. The calls that actually happened, not grep. what_it_calls does the reverse.

See more →

specs_that_reach

CLI · LSP · MCP free

Run only the specs that matter. The spec examples whose recorded runs reach a method, so a change reruns those and nothing else. Keeps a red-green loop in seconds.

See more →

how_to_reach

MCP free

Reach any method. The collaborators and call path that get an object into the state a method needs, taken from a real run. Setup for tests that use real objects instead of doubles.

See more →