Inspect Your Event Store From the Terminal With the res CLI

… and check why 5600+ Rails engineers read also this

Inspect Your Event Store From the Terminal With the res CLI

Because not every debugging session needs rails c.

Most event store debugging starts the same way: you open a Rails console, try to remember the Event Store read API, and type something like:

event_store.read.stream("Order$123").to_a

It works, but it’s a lot of ceremony just to answer questions like:

  • What happened in this stream?
  • Was this event actually persisted?
  • What does the latest event look like?

The new contrib gem, ruby_event_store-cli, gives you a dedicated res command for inspecting your Event Store directly from the terminal. No Rails console. No SQL queries. No custom rake tasks. Just a fast, convenient way to browse streams and events while you’re developing or debugging.

Add it to your Gemfile:

gem "ruby_event_store-cli"

The CLI loads your Rails environment, so it connects to the same Rails.configuration.event_store your application uses. Once it’s running, you can inspect streams, read events, search across the store, trace correlations, or watch new events arrive — all without leaving your terminal. Here’s a quick taste:

bundle exec res stream show Order$<order_id>
bundle exec res stream events Order$<order_id> --limit 20
bundle exec res event show 59588873-00fa-423f-89d1-03d7c3b0ab35
bundle exec res search --type OrderPlaced --after 2026-03-01T00:00:00Z
bundle exec res stats

Say an order never gets confirmed. Instead of reaching for rails c, you can walk the whole story from the terminal:

bundle exec res stream show Order$<order_id>
bundle exec res stream events Order$<order_id>
bundle exec res trace <correlation_id>

In under a minute you’ve gone from “something’s wrong” to seeing exactly where the workflow stopped. The rest of this post walks through each command in detail.

Streams

stream show — a stream at a glance

Event count, current version, and the first and last event:

$ bundle exec res stream show 'Order$f47ac10b-58cc-4372-a567-0e02b2c3d479'
Stream:  Order$f47ac10b-58cc-4372-a567-0e02b2c3d479
Events:  2
Version: 1
First:   2026-03-24T17:45:03.891Z (OrderPlaced)
Last:    2026-03-24T17:46:12.004Z (OrderConfirmed)

stream events — read the events

By default it prints the stream as a table:

$ bundle exec res stream events Order$<order_id>
EVENT ID                              TYPE                                      TIMESTAMP
------------------------------------------------------------------------------------------
59588873-00fa-423f-89d1-03d7c3b0ab35  OrderPlaced                               2026-03-24T17:45:03.891Z
6f1c2d3e-4a5b-4c6d-8e9f-0a1b2c3d4e5f  OrderConfirmed                            2026-03-24T17:46:12.004Z

2 event(s)

Narrow it down with filters — they combine freely:

# Filter by event type
bundle exec res stream events Order$<order_id> --type OrderPlaced

# Newer than a timestamp (ISO8601)
bundle exec res stream events Order$<order_id> --after 2026-03-01T00:00:00Z

# Older than a timestamp
bundle exec res stream events Order$<order_id> --before 2026-04-01T00:00:00Z

# Cap the number of events (default: 50)
bundle exec res stream events Order$<order_id> --limit 10

# Paginate — start after a known event ID
bundle exec res stream events Order$<order_id> --from 3fa85f64-5717-4562-b3fc-2c963f66afa6

Add --follow (or -f) to keep the command running and print new events in this stream as they arrive — a live tail of a single stream:

bundle exec res stream events Order$<order_id> --follow

Events

event show — everything about one event

Full detail for a single event ID, data and metadata included:

$ bundle exec res event show 59588873-00fa-423f-89d1-03d7c3b0ab35
Event ID:   59588873-00fa-423f-89d1-03d7c3b0ab35
Type:       OrderPlaced
Timestamp:  2026-03-24T17:45:03.891Z
Valid at:   2026-03-24T17:45:03.891Z
Data:       {
  "order_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "customer_id": "c-1024",
  "total": "149.00"
}
Metadata:   {
  "correlation_id": "452fd6f0-e3a2-4716-bc8a-43bbcf2cae61",
  "causation_id": "452fd6f0-e3a2-4716-bc8a-43bbcf2cae61"
}

event streams — where an event lives

An event is published to one stream and can be linked into many. This lists every stream it belongs to:

$ bundle exec res event streams 59588873-00fa-423f-89d1-03d7c3b0ab35
Order$f47ac10b-58cc-4372-a567-0e02b2c3d479
$by_type_OrderPlaced
$by_correlation_id_452fd6f0-e3a2-4716-bc8a-43bbcf2cae61

Search across the store

search — find events anywhere

stream events is scoped to one stream; search looks across the whole store. Same filters — --type, --after, --before, --limit — plus --stream to scope back down when you want to:

$ bundle exec res search --type OrderPlaced --after 2026-03-01T00:00:00Z
EVENT ID                              TYPE                                      TIMESTAMP
------------------------------------------------------------------------------------------
59588873-00fa-423f-89d1-03d7c3b0ab35  OrderPlaced                               2026-03-24T17:45:03.891Z
8b1f2c3d-1a2b-4c3d-9e8f-7a6b5c4d3e2f  OrderPlaced                               2026-03-22T09:13:55.620Z

2 event(s)

stats — counts and types

Total event count and the unique event types present — across the store, or for one stream with --stream:

$ bundle exec res stats
Events:  1432

Event types:
  OrderCancelled
  OrderConfirmed
  OrderPlaced
  PaymentAuthorized
  ShipmentScheduled
$ bundle exec res stats --stream Order$<order_id>
Stream:  Order$f47ac10b-58cc-4372-a567-0e02b2c3d479
Events:  2

Event types:
  OrderConfirmed
  OrderPlaced

JSON output

stream events and search can both emit JSON instead of a table with --format json. That makes them easy to combine with tools like jq or pipe into your own scripts:

# What data did our last OrderPlaced carry?
bundle exec res stream events Order$<order_id> \
  --type OrderPlaced \
  --limit 1 \
  --format json \
  | jq '.[0].data'

# Any events with missing customer_id?
bundle exec res search --type OrderPlaced --format json \
  | jq '.[] | select(.data.customer_id == null)'

# Unique event types in a stream
bundle exec res stream events Order$<order_id> --format json \
  | jq '[.[].event_type] | unique'

Each JSON event carries event_id, event_type, data, metadata, and timestamp — the keys the jq filters above reach into.

Trace correlated events

Give res trace a correlation ID and it gathers every event sharing it, then shapes them by causation — which event triggered which — into a tree:

$ bundle exec res trace 452fd6f0-e3a2-4716-bc8a-43bbcf2cae61
OrderPlaced [452fd6f0-e3a2-4716-bc8a-43bbcf2cae61]
├── PaymentAuthorized [7c9e6679-7425-40de-944b-e07fc1f90ae7]
│   └── OrderConfirmed [a1b2c3d4-5e6f-4a8b-9c0d-1e2f3a4b5c6d]
└── ShipmentScheduled [9f8e7d6c-5b4a-4938-8271-6f5e4d3c2b1a]

The tree makes it obvious where a workflow stopped. If a branch is missing, you immediately know which handler never published its follow-up event — without digging through logs.

trace relies on correlation linking. As related events are published, Rails Event Store links them into a $by_correlation_id_... stream while preserving correlation and causation IDs. The command simply reconstructs the tree from those links.

Watch events live

res watch gives you a live view of everything happening in the event store, grouped by bounded-context namespace (the part before :: in the event type). Handy during demos, load tests, or while reproducing a multi-step flow:

$ bundle exec res watch
Fulfillment (2 events)
  OrderPlaced                     17:45:03  452fd6f0-e3a2-4716-bc8a-43bbcf2cae61
  OrderConfirmed                  17:45:04  a1b2c3d4-5e6f-4a8b-9c0d-1e2f3a4b5c6d

Payments (1 events)
  PaymentAuthorized               17:45:03  7c9e6679-7425-40de-944b-e07fc1f90ae7

Watching since 17:45:00 — Press Ctrl+C to exit

The display updates in place instead of scrolling. A few flags control what it shows and how often:

  • --namespace Fulfillment,Payments — only show those namespaces
  • --since 2026-03-24T17:00:00Z — start from a timestamp instead of “now”
  • --limit 50 — max events shown per namespace (default: 50)
  • --interval 1 — refresh interval in seconds (default: 1)

Where stream events --follow tails one stream, watch gives you the whole store at a glance — two complementary live views.

The full command set

Eight commands in this first release, all reading through the public event_store API — no SQL, no adapter internals:

Command What it does
stream show Event count, version, and first/last event of a stream
stream events Read a stream’s events, with filtering, pagination, and --follow
event show Full detail of one event — data, metadata, timestamps
event streams Every stream an event is published or linked to
search Find events across all streams by type, time, or stream
stats Total count and unique event types, store-wide or per stream
trace Causation tree for everything sharing a correlation ID
watch Live feed of new events, grouped by namespace

The CLI intentionally stays small. Every command goes through the public Rails Event Store API, so it works regardless of the storage adapter underneath. If you spend your day debugging event-driven applications, it’s often the quickest way to answer “what happened?” without opening rails c.

See also

Prefer letting an AI assistant do the reading? Its companion gem, res-mcp, exposes the same event store as MCP tools — you ask in plain English instead of typing commands.

You might also like