> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt
> Use this file to discover all available pages before exploring further.

# MCAP Capture

> Record per-test MCAP bag files for failure forensics. Topic allowlists, storage costs, and how MCAPs surface in the test-run detail UI.

# MCAP capture

MCAP is the open-source successor to ROS bag files. Roboticks's `mcap_capture` context manager records the topics you care about, only during the test, and uploads the resulting `.mcap` to the platform automatically when the test fails (or always, if you ask).

<Info>
  Per-test recording, **not** session-wide. A failing nightly suite produces one MCAP per failing test — no 30-GB monolith to scrub through.
</Info>

## Install

MCAP is an optional extra to keep the SDK install light for users who don't record:

```bash theme={null}
pip install 'roboticks[mcap]'
```

This pulls in `mcap` and `mcap-ros2-support`. Without the extra, importing the context manager raises a clear error pointing at this install line.

## The context manager

```python theme={null}
from roboticks import mcap_capture

with mcap_capture(
    topics=["/cmd_vel", "/scan", "/odom"],   # allowlist
    path="captures/test_estop.mcap",         # local path
    upload_on="failure",                     # "failure" | "always" | "never"
) as bag:
    run_the_scenario()
```

Parameters:

| Parameter     | Type                                    | Default                     | Behaviour                                                      |
| ------------- | --------------------------------------- | --------------------------- | -------------------------------------------------------------- |
| `topics`      | `list[str]`                             | `["*"]`                     | Topic allowlist. Wildcards permitted. Subscribe-on-first-seen. |
| `path`        | `str \| Path`                           | auto, under `test_results/` | Where to write the MCAP locally.                               |
| `upload_on`   | `Literal["failure", "always", "never"]` | `"failure"`                 | When the runner uploads the bag to platform storage.           |
| `compression` | `Literal["zstd", "lz4", "none"]`        | `"zstd"`                    | Per-chunk compression.                                         |

Full reference: [SDK MCAP capture](/sdk/mcap-capture).

## Use the allowlist

The allowlist is the difference between a 3 MB bag and a 3 GB one. Subscribe-to-all is rarely what you want; most safety-relevant tests only need 2–5 topics.

```python theme={null}
with mcap_capture(topics=["/cmd_vel", "/safety/*"]):
    ...
```

Wildcards follow rosbag2 semantics: `/safety/*` matches every immediate child of `/safety`; `**` matches recursively.

## A typical safety test

```python theme={null}
import pytest
from roboticks import confirms, deadline, mcap_capture
from roboticks.assertions import assert_topic_published
from std_msgs.msg import Bool

@confirms("REQ-014")
@deadline(milliseconds=200)
def test_estop_with_recording(ros_context, robot_node):
    with mcap_capture(
        topics=["/cmd_vel", "/safety/estop_engaged", "/safety/heartbeat"],
        upload_on="failure",
    ):
        robot_node.command_velocity(1.0)
        robot_node.trigger_estop()
        assert_topic_published(
            "/safety/estop_engaged", Bool,
            within=0.1, predicate=lambda m: m.data is True,
        )
```

When this test fails, the platform shows the MCAP under the test-run detail page. When it passes, no upload, no storage charge.

## How MCAPs surface in the UI

Each failing test in the [run detail UI](/runs/detail) gets:

1. A **download link** to the raw `.mcap`.
2. A **Foxglove deep-link** that opens the bag in Foxglove Studio with the relevant topics pre-selected.
3. An **LLM triage** block that references timestamps inside the bag ("at t=4.2 s, `/safety/estop_engaged` is still `false`; expected `true` per @deadline(200)").

## Storage cost

MCAPs land in S3 standard for 90 days, then Glacier Deep Archive. See [pricing](/pricing).

* **Free tier**: MCAPs auto-expire after 30 days.
* **Team / Enterprise**: $0.05 / GB-month hot, $0.005 / GB-month archive.

A typical safety test produces 200 KB to 5 MB depending on topic rate. A nightly suite of 1,000 tests with 5% failure rate (`upload_on="failure"`) and average 2 MB per failure is \~100 MB / night, or \~3 GB / month — well under \$1 / month at hot pricing.

<Tip>
  Set `upload_on="always"` on the handful of tests that *always* benefit from a bag (interactive perception tuning, regression hunts). Leave the rest on `"failure"`.
</Tip>

## Multi-test sessions

For session-scoped recording (one bag per session, not per test) use a session-level pytest fixture instead. That's an explicit choice — the default is per-test for noise isolation.

```python theme={null}
@pytest.fixture(scope="session")
def session_bag():
    with mcap_capture(path="captures/full_session.mcap", upload_on="always") as bag:
        yield bag
```

## Interaction with fault injection

`mcap_capture` records the **post-fault** topic — exactly what the system under test observed. Pair it with `delay_messages` or `drop_messages` to capture the degraded signal an auditor will want to see:

```python theme={null}
with mcap_capture(topics=["/scan", "/cmd_vel"]):
    with delay_messages("/scan", ms=300):
        ...
```

## Next

<CardGroup cols={2}>
  <Card title="MCAP API reference" icon="book" href="/sdk/mcap-capture">
    Full parameter list and edge cases.
  </Card>

  <Card title="Fault injection" icon="bug" href="/testing/fault-injection">
    Often paired with MCAP for failure forensics.
  </Card>

  <Card title="LLM triage" icon="wand-magic-sparkles" href="/llm/triage">
    How the platform reads your MCAP to explain failures.
  </Card>

  <Card title="Pricing" icon="dollar-sign" href="/pricing">
    Storage costs in detail.
  </Card>
</CardGroup>
