Recording & Replaying Data with rosbag2
Testing on live hardware every time you tweak a line of code is slow and sometimes dangerous. Learn rosbag2: how to record everything your robot's topics say, replay it on demand, and use it to debug and test without touching real hardware.
Say your perception node has a bug that only shows up when the robot drives past a particular reflective surface. To debug it today, you’d need the robot, that exact hallway, and however many attempts it takes to reproduce the glitch. That’s a lot of setup for one bug fix — and if the reflective surface is a rare stretch of hallway on the third floor, you might be doing a lot of walking.
rosbag2 solves this by letting you record everything your topics published once, then replay that exact sequence of messages as many times as you want — no robot, no hallway, no walking. This week: recording, inspecting, and replaying data, plus the storage-format choice that trips up almost everyone the first time.
What You’ll Learn
- What a “bag” actually is and why it’s the backbone of offline debugging and regression testing
- How to record topics with
ros2 bag record, and the tradeoffs of recording everything versus a specific list - The two storage formats —
mcapandsqlite3— and which one you actually want - How to inspect a bag with
ros2 bag infoand replay it withros2 bag play - The common pitfalls that turn a bag from a useful tool into a confusing one
What a Bag Actually Is
A bag is just a file (or small folder of files) containing a timestamped log of every message that crossed one or more topics during a recording session. Under the hood, ros2 bag record is a node like any other: it subscribes to the topics you tell it to, and instead of doing something with each message, it writes the message, its topic name, and its arrival timestamp to disk.
That simple idea unlocks a lot:
- Debugging without hardware. Capture the exact sensor stream that triggered a bug once, then replay it on your laptop as many times as it takes to fix.
- Regression testing. Keep a library of bags — “robot approaching a doorway,” “IMU during a hard turn” — and replay them against new code to check nothing broke.
- Sharing data. Send a teammate a bag instead of asking them to reproduce a scenario on their own hardware.
- Offline development. Build and test a node that consumes point cloud or SLAM data entirely from a recording, before the real sensor even arrives on your bench.
To playing nodes, a bag is indistinguishable from the real thing: ros2 bag play republishes messages on the same topic names with the same types, so a node built to consume /scan or /camera/image_raw doesn’t know or care whether the data is coming from live hardware or a file on disk.
Recording Data
The basic command subscribes to whatever topics you name and writes them to a bag as they arrive:
# Record two specific topics
ros2 bag record /scan /odom
# Record every topic currently visible, and pick up new ones that appear mid-recording
ros2 bag record -a
# Give the output a specific name instead of the default timestamped one
ros2 bag record -o hallway_bug_repro /scan /odom /tf
Recording specific topics is almost always the better default. -a is convenient for “capture everything, sort it out later,” but on a robot with high-rate camera or point cloud topics it can fill a disk in minutes. Recording only what you actually need keeps bags small and fast to replay.
A bag is a directory, not a single file — it holds one or more data files plus a metadata.yaml describing what’s inside. Stop a recording with Ctrl-C; the metadata gets written out cleanly, so avoid killing the process with kill -9, which can leave a bag without valid metadata.
Choosing a Storage Format
ros2 bag record writes to one of two storage plugins, set with --storage:
mcap (default since ROS 2 Iron) | sqlite3 (the original default) | |
|---|---|---|
| Write throughput | Higher — better for high-rate topics like cameras | Lower under heavy load |
| Tooling | Readable by Foxglove and other non-ROS tools | ROS-specific; needs ros2 bag or a SQLite client |
| File layout | One .mcap file per bag (usually) | One .db3 file per bag |
| When to reach for it | Default choice for new recordings | Reading an older bag, or a workflow that specifically expects SQLite |
Unless you have a reason to do otherwise, don’t pass --storage at all — the default is mcap, and it’s the right choice for nearly every recording you’ll make. You’ll mostly need --storage sqlite3 when you’re re-generating an old bag format for a tool that hasn’t caught up, or when opening a .db3 bag someone handed you from an older setup.
Inspecting a Bag
Before replaying anything, ros2 bag info tells you what’s actually in the file — topic names, message counts, types, and duration:
ros2 bag info hallway_bug_repro
Files: hallway_bug_repro.mcap
Bag size: 48.2 MiB
Storage id: mcap
Duration: 32.104s
Start: Jul 20 2026 09:14:02.331
End: Jul 20 2026 09:14:34.435
Messages: 9841
Topic information: Topic: /scan | Type: sensor_msgs/msg/LaserScan | Count: 320
Topic: /odom | Type: nav_msgs/msg/Odometry | Count: 3201
Topic: /tf | Type: tf2_msgs/msg/TFMessage | Count: 6320
This is worth running out of habit before you play anything back — it catches the common mistake of recording the wrong topic name (a typo means an empty bag with zero messages on that topic, not an error).
Playing Data Back
ros2 bag play republishes the recorded messages on their original topics, in their original order and timing:
# Play back at recorded speed
ros2 bag play hallway_bug_repro
# Play back at half speed — useful for watching a fast event closely
ros2 bag play hallway_bug_repro -r 0.5
# Loop continuously, handy for a long-running visual check
ros2 bag play hallway_bug_repro -l
# Replay only a subset of topics
ros2 bag play hallway_bug_repro --topics /scan /tf
While it’s playing, any node subscribed to /scan, /odom, or /tf behaves exactly as it would with the real robot running — you can point RViz at it, feed it into a perception node, or drive it through a bug you’re chasing, all without hardware attached.
If the nodes consuming the bag expect simulation time rather than wall-clock time (for example, anything using use_sim_time, the same parameter from Week 27’s launch files), add --clock:
ros2 bag play hallway_bug_repro --clock 100
This makes ros2 bag play publish to /clock at 100 Hz while it plays, so every node with use_sim_time:=true — including lifecycle-managed stacks like Nav2 — stays in sync with the recorded timestamps instead of the wall clock.
Common Pitfalls
Reaching for -a on a bandwidth-heavy robot. A single 1080p camera topic can produce gigabytes per minute. Record specific topics unless you genuinely need everything, and check ros2 topic hz <topic> beforehand if you’re unsure how heavy a topic is.
Killing a recording with kill -9 or a hard power-off. The bag’s metadata.yaml is finalized when recording stops cleanly. An interrupted recording can leave a bag that ros2 bag info can’t fully read. Always stop with Ctrl-C and give it a moment to flush.
Forgetting --clock when downstream nodes expect simulation time. If a node was launched with use_sim_time:=true and nothing is publishing to /clock, that node’s internal clock never advances — timestamps stay frozen and anything time-dependent (like tf2 transform lookups) silently fails to find data. Pass --clock during playback whenever the consuming nodes use sim time.
Assuming playback rate doesn’t matter. Played back too fast (-r 2.0 or higher) on a slow subscriber, messages can arrive faster than the node can process them, and its input queue silently drops the overflow. If a playback-driven test behaves differently than the live robot did, try -r 1.0 or slower before assuming the bug is in your code.
Mixing up topics recorded vs. topics needed. ros2 bag play only republishes what was actually recorded. If your test node also needs /tf_static or a parameter that isn’t a topic at all, a bag alone won’t supply it — you may still need to launch a small supporting node or set static parameters manually alongside playback.
Recap
| What you want | How to do it |
|---|---|
| Record specific topics | ros2 bag record /topic1 /topic2 |
| Record everything currently visible | ros2 bag record -a |
| Name the output bag | ros2 bag record -o <name> ... |
| See what’s inside a bag | ros2 bag info <bag> |
| Replay at recorded speed | ros2 bag play <bag> |
| Replay slower / faster | ros2 bag play <bag> -r <rate> |
| Loop playback | ros2 bag play <bag> -l |
| Keep sim-time nodes synced during playback | ros2 bag play <bag> --clock <hz> |
A bag turns a one-time, hard-to-reproduce moment on real hardware into a file you can replay a hundred times from your desk. Combined with the topics, services, and lifecycle nodes you’ve built over the last few weeks, it’s the tool that makes offline debugging and regression testing actually practical.
Next week is our regular news roundup: Dispatch #10, a look at what’s actually shipping, deploying, and getting funded across robotics this summer — and how it connects back to the ROS 2 tools you’ve been building with these last few weeks.