Replay & verify
bugcapsule CLI
One CLI (Dart, shipped in the Flutter package, version 0.4.0) replays capsules from every runtime. It drives flutter run, an Android instrumentation, an XCUITest or a Playwright browser, and computes verdicts by the same rules everywhere.
Install
# From git
dart pub global activate --source git https://github.com/draz26648/bug_capsule_flutter_sdk --git-path packages/bug_capsule
# From a local checkout (what the onboarding uses)
cd bug_capsule_flutter_sdk && flutter pub get && dart pub global activate --source path packages/bug_capsule
# Inside a Flutter app that already depends on bug_capsule
dart run bug_capsule:bugcapsule <command>
bugcapsule --version # bugcapsule 0.4.0 (capsule spec v1)
The package depends on the Flutter SDK. If dart pub global activate fails to resolve, use the dart bundled with Flutter (flutter/bin/dart). Upgrading from the old bug_capsule_cli package: dart pub global deactivate bug_capsule_cli first.
Log in
bugcapsule login --url https://api.bugcapsule.com --secret-key bcs_... --ingest-key bci_...
Credentials go to ~/.bugcapsule/config.json (mode 600; USERPROFILE on Windows). BUGCAPSULE_URL, BUGCAPSULE_SECRET_KEY and BUGCAPSULE_INGEST_KEY override the file, which is what you want in CI. The ingest key is only needed for push.
Commands
| Command | What it does | Key options |
|---|---|---|
inspect <file|id|share-url> | Failure, eligibility, capabilities, network, timeline | --json, --timeline N (40) |
run <ref> | Replay; expects REPRODUCED. Builds the capsule's commit in a temporary git worktree. | -n (3), --[no-]pin, --report-to <serverId> |
verify <ref> | Replay on your working tree; expects FIXED | -n (5), --report-to |
regress add <ref> | Keeps the capsule as a regression test in bugcapsule/regressions.json + bugcapsule/regressions/<id>.bugcapsule | -p, --name |
regress list | Tests and their last result | -p |
regress run | Runs every active test; exit 1 on any failure | -n (2), --include-quarantined |
list | Your project's capsules, newest first | --status, --mode, --eligibility, --limit (25) |
pull <id|url> | Download a .bugcapsule | -o |
push <file> | Upload a local capsule (ingest key) | |
share <id> | Expiring public download link | --hours (72, max 720) |
capture | Show or change remote capture config | --on, --off, --sample-rate |
bugcapsule capture --on turns the kill switch on, which disables capture. --off resumes capture. With no flag it prints the current config. Production sampling: bugcapsule capture --sample-rate 0.05.
Launch options (run, verify, regress run)
| Option | Default | Meaning |
|---|---|---|
--runtime | detected | flutter, android, ios, web |
-d, --device | Simulator name/UDID, Android serial/AVD/model | |
-p, --project | . | Project root (where bugcapsule.json lives) |
-t, --target, --flavor, --dart-define | lib/main.dart | Flutter build options |
--apk + --test-apk, --xctestrun, --app-url | Prebuilt artifacts (Android, iOS, web); skip build and pinning | |
--attempt-timeout | recorded duration + settle + 45 s | Seconds per attempt |
--lan | off | Bind the replay server on the LAN (physical devices) |
--strategy | auto | auto, pointer, semantic |
--settle | 8 | Seconds to wait for the failure after the last input |
--lenient-network | off | Send unrecorded requests to the real network (still reported) |
--[no-]compress-idle | on | Shrink idle gaps over 1.5 s |
-v, --verbose |
Exit codes
| Code | run | verify | Also |
|---|---|---|---|
0 | REPRODUCED | FIXED | Any successful non-replay command |
1 | NOT REPRODUCED | STILL FAILING | API, state and socket errors; a failing regress run |
2 | FLAKY | FLAKY | |
3 | INCONCLUSIVE / INCOMPATIBLE | INCONCLUSIVE / INCOMPATIBLE | Invalid capsule file |
64 | Usage error | ||
130 | Interrupted (Ctrl-C); the attempt's processes are killed and cleaned up | ||
How attempts become verdicts: Verdicts & divergences.
Which runtime, which code
--runtime, else"runtime"inbugcapsule.jsonat the project root, else detection:pubspec.yaml→ flutter,settings.gradle(.kts)→ android,*.xcodeproj/*.xcworkspace→ ios,package.json→ web.- A capsule replays only in the runtime that captured it. Otherwise:
INCOMPATIBLE(runtime.mismatch, exit 3) without building anything. runbuildsmanifest.source.gitShain a temporary git worktree when the project is a git checkout and the commit exists (--no-pinreplays the working tree).verifyandregress runbuild the working tree.- The build gets the commit it's made from: Flutter
--dart-define=BUGCAPSULE_GIT_SHA, Android-PbugcapsuleGitSha, iOS build settingBUGCAPSULE_GIT_SHA, web envBUGCAPSULE_GIT_SHA. A commit other than the capsule's is the expectedbuild.gitShadivergence.
bugcapsule.json
At the project root. Not needed for Flutter apps that use the defaults.
{
"runtime": "android",
"android": { "applicationId": "com.example.shop" },
"timeouts": { "buildSeconds": 1200, "attemptSeconds": 120 }
}
| Key | Default | Meaning |
|---|---|---|
applicationId | required | |
gradleDir | . | Gradle root, relative to this file |
module / moduleDir | app | Module path / directory for APK lookup |
variant | debug | |
testApplicationId | <applicationId>.test | |
runner, testClass | BugCapsule runner / test | |
avd | Booted headless when no device is running | |
gradleArgs, emulatorArgs | [] | Lists |
sdkDir | ANDROID_HOME, local.properties, ~/Library/Android/sdk | |
bootTimeoutSeconds | "300" | Write it as a string: the CLI reads this key as a string and rejects a JSON number |
Env BUGCAPSULE_ANDROID_GRADLE_ARGS appends Gradle arguments for one run.
| Key | Default | Meaning |
|---|---|---|
project / workspace | -project / -workspace | |
scheme | required | Builds the app and the UI test target |
configuration | Debug | Must define DEBUG |
bundleId | from the built app | Required with --xctestrun |
replayTest | <uiTestTarget>/BugCapsuleReplayTests/testReplay | uiTestTarget defaults to UITests; set one of them |
simulator | first booted iPhone | Name substring or UDID |
derivedDataPath | $TMPDIR/bugcapsule-ios-<hash> | Reused across runs |
startupSeconds | 120 | Added to each attempt's budget |
buildSettings | Map of extra settings |
Env BUGCAPSULE_IOS_BUILD_SETTINGS='K=V K2=V2' adds settings for one run.
| Key | Default | Meaning |
|---|---|---|
url | http://127.0.0.1:4317/ | Where the served app lives |
installCommand + installDir | Run when node_modules is missing (pinned worktrees) | |
buildCommand, serveCommand | ||
readyTimeoutSeconds | 60 | Integer |
allowOrigins | Origins the page may load besides the app's | |
runner | @bugcapsule/web-replay bin | |
headed | false | Watch the browser |
env | Map passed to every command | |
browser | chromium | Only Chromium |
No section needed. Use -t, --flavor and --dart-define on the command line. timeouts applies as for every runtime.
| Shared key | Default |
|---|---|
timeouts.buildSeconds | 1200 |
timeouts.attemptSeconds | recorded duration + settle + 45 s |
Replay protocol and safety
Protocol v1.1, identical across runtimes:
BUGCAPSULE_REPLAY_URL = http://127.0.0.1:<port>/a/<token>
GET <base>/settings → ReplaySettings JSON (attemptId, runtime, strategy, …)
GET <base>/capsule → .bugcapsule bytes
POST <base>/result → ReplayReport JSON (204; 409 wrong attemptId; 410 attempt closed)
- The server binds 127.0.0.1 on a random port (
--lanfor physical devices). Android emulators reach it throughadb reverse(removed on exit). - Every attempt gets a new 32-byte token and a new
attemptId. Other tokens get 404; a report is accepted once; a late report from a killed attempt can never count for the next one. - Requests with an
Originheader are refused (403), so no web page can talk to the server. - Fallback result channel:
BUGCAPSULE_RESULT::{json}on stdout (Flutter), in instrumentation status (Android) or the XCUITest log (iOS), accepted only from the process launched for the armed attempt. - Attempt isolation: Flutter relaunches, Android
pm clear, iOS uninstall, web fresh browser context. A missing report, crash or timeout is anerrorattempt, never evidence of a fix.
Environment variables
| Variable | Used for |
|---|---|
BUGCAPSULE_URL, BUGCAPSULE_SECRET_KEY, BUGCAPSULE_INGEST_KEY | Credentials (override ~/.bugcapsule/config.json) |
BUGCAPSULE_ANDROID_GRADLE_ARGS, ANDROID_HOME, ANDROID_SDK_ROOT | Android launcher |
BUGCAPSULE_IOS_BUILD_SETTINGS | iOS launcher |
NO_COLOR | Plain output |
BUGCAPSULE_GIT_SHA, BUGCAPSULE_GIT_DIRTY, BUGCAPSULE_REPLAY_URL | Set by the CLI for the app being built or replayed |