Available in the TypeScript, Python, and Ruby SDKs (the ones with replay +
ReplayEnvironment). The Go SDK has no replay, so database branching does not apply.How it works
Capture is automatic: every root trace already records the wall-clock instant it ran, with no SDK configuration. So there’s nothing to turn on; setup is just two steps:- Connect your database once in the Bitfab dashboard. Your source database can be any Postgres; Bitfab provisions a managed, branchable copy from it.
- Wire replay to read the per-trace branch URL through a
ReplayEnvironment.
1. Connect your database
In the Bitfab dashboard, open Integrations → Database and paste your Postgres connection string. Bitfab provisions a branchable managed copy from it; discovery and engine setup take a few minutes, after which the Database section shows Connected. Your source database can be any Postgres. You do not set anyNEON_* environment variables (those are Bitfab-side server configuration). You only provide your source connection string in the dashboard. The connection string is encrypted at rest, and replay branches are isolated, ephemeral copies; replay writes never touch your source database.
2. Wire replay to the branch
Construct oneReplayEnvironment, pass it to the replay call, and connect through its branch URL inside the replayed function. The construction call, the replay option, and the accessors differ slightly per SDK.
Reading the environment
The environment is active only inside a replay item that has a resolved branch. Always check that first:- On the live request path, the environment is inactive: your function keeps using its normal
DATABASE_URL. The same code works in production and in replay. - For traces captured before the SDK version that added always-on snapshot capture, the environment is inactive (no snapshot ref), so the item replays against your normal database.
- Reading the branch URL while inactive throws. Gate every read on the active flag.
| TypeScript | Python | Ruby | |
|---|---|---|---|
| Active flag | env.active | env.active | env.active? |
| Branch URL | env.databaseUrl | env.database_url | env.database_url |
| Expiry | env.expiresAt | env.expires_at | env.expires_at |
| Read-only | env.readOnly | env.read_only | env.read_only |
| Source trace | env.traceId | env.trace_id | env.trace_id |
Resolve the connection per call, not at import
The most common reason a wired replay still hits production is a database client created once at module import: a module-level pool/engine bound toDATABASE_URL captures that value before any replay context exists. Refactor so the connection string is resolved per call (or per replay item) and your replayed function can build, or be handed, a client from the environment’s branch URL. If you can’t move off an import-time pool, run replay in a process where DATABASE_URL is set to the branch URL before the module loads.
Verifying it works
Capture is automatic, but a trace only carries a snapshot ref if it was recorded by an SDK version with always-on capture, so smoke-test with a recently captured trace:- Run the instrumented function once so a new trace lands.
- Replay that trace and confirm, inside the function, that the environment is active and its branch URL’s host differs from your normal
DATABASE_URL. - Open the test run URL from the replay result to inspect the experiment.
Bitfab tracks whether the branch was actually used
Each replayed trace records whether your code actually obtained the branch URL during that item (readingdatabaseUrl / database_url or calling snapshot() counts; checking the active flag alone does not). The experiment data for the trace then shows one of three states:
- Used: a branch was provisioned and your function took its URL.
- Provisioned but never read: a branch was ready, but the function never asked for the URL. This usually means the replay silently hit your live database; the most common cause is a connection pool created at module import (see “Resolve the connection per call, not at import” above).
- No branch: the item had no snapshot to branch from, or branch provisioning failed, and the function ran against its normal database.
Optional: pin the provider (TypeScript)
Capture works with no configuration. In TypeScript you may pass a provider to pin it at capture time; it is not required, and the provider is otherwise resolved at replay time:Limitations
- TypeScript, Python, and Ruby only (Go has no replay).
- Postgres only.
- Branch leases are short-lived (a few minutes) and created fresh per replay item. Replay completes well within that window; the lease is released and the branch deleted afterward.
- The branch reflects the source database’s state at the captured instant, bounded by replication lag (typically sub-second to a few seconds).
- Replay writes land only on the ephemeral branch and are discarded; they never propagate to your source database.
- Only traces captured by an SDK version with always-on snapshot capture can be branched. Older traces replay against your normal database.