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)
i

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

CommandWhat it doesKey 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 listTests and their last result-p
regress runRuns every active test; exit 1 on any failure-n (2), --include-quarantined
listYour 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)
captureShow 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)

OptionDefaultMeaning
--runtimedetectedflutter, android, ios, web
-d, --deviceSimulator name/UDID, Android serial/AVD/model
-p, --project.Project root (where bugcapsule.json lives)
-t, --target, --flavor, --dart-definelib/main.dartFlutter build options
--apk + --test-apk, --xctestrun, --app-urlPrebuilt artifacts (Android, iOS, web); skip build and pinning
--attempt-timeoutrecorded duration + settle + 45 sSeconds per attempt
--lanoffBind the replay server on the LAN (physical devices)
--strategyautoauto, pointer, semantic
--settle8Seconds to wait for the failure after the last input
--lenient-networkoffSend unrecorded requests to the real network (still reported)
--[no-]compress-idleonShrink idle gaps over 1.5 s
-v, --verbose

Exit codes

CoderunverifyAlso
0REPRODUCEDFIXEDAny successful non-replay command
1NOT REPRODUCEDSTILL FAILINGAPI, state and socket errors; a failing regress run
2FLAKYFLAKY
3INCONCLUSIVE / INCOMPATIBLEINCONCLUSIVE / INCOMPATIBLEInvalid capsule file
64Usage error
130Interrupted (Ctrl-C); the attempt's processes are killed and cleaned up

How attempts become verdicts: Verdicts & divergences.

Which runtime, which code

  1. --runtime, else "runtime" in bugcapsule.json at the project root, else detection: pubspec.yaml → flutter, settings.gradle(.kts) → android, *.xcodeproj/*.xcworkspace → ios, package.json → web.
  2. A capsule replays only in the runtime that captured it. Otherwise: INCOMPATIBLE (runtime.mismatch, exit 3) without building anything.
  3. run builds manifest.source.gitSha in a temporary git worktree when the project is a git checkout and the commit exists (--no-pin replays the working tree). verify and regress run build the working tree.
  4. The build gets the commit it's made from: Flutter --dart-define=BUGCAPSULE_GIT_SHA, Android -PbugcapsuleGitSha, iOS build setting BUGCAPSULE_GIT_SHA, web env BUGCAPSULE_GIT_SHA. A commit other than the capsule's is the expected build.gitSha divergence.

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 }
}
KeyDefaultMeaning
applicationIdrequired
gradleDir.Gradle root, relative to this file
module / moduleDirappModule path / directory for APK lookup
variantdebug
testApplicationId<applicationId>.test
runner, testClassBugCapsule runner / test
avdBooted headless when no device is running
gradleArgs, emulatorArgs[]Lists
sdkDirANDROID_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.

KeyDefaultMeaning
project / workspace-project / -workspace
schemerequiredBuilds the app and the UI test target
configurationDebugMust define DEBUG
bundleIdfrom the built appRequired with --xctestrun
replayTest<uiTestTarget>/BugCapsuleReplayTests/testReplayuiTestTarget defaults to UITests; set one of them
simulatorfirst booted iPhoneName substring or UDID
derivedDataPath$TMPDIR/bugcapsule-ios-<hash>Reused across runs
startupSeconds120Added to each attempt's budget
buildSettingsMap of extra settings

Env BUGCAPSULE_IOS_BUILD_SETTINGS='K=V K2=V2' adds settings for one run.

KeyDefaultMeaning
urlhttp://127.0.0.1:4317/Where the served app lives
installCommand + installDirRun when node_modules is missing (pinned worktrees)
buildCommand, serveCommand
readyTimeoutSeconds60Integer
allowOriginsOrigins the page may load besides the app's
runner@bugcapsule/web-replay bin
headedfalseWatch the browser
envMap passed to every command
browserchromiumOnly Chromium

No section needed. Use -t, --flavor and --dart-define on the command line. timeouts applies as for every runtime.

Shared keyDefault
timeouts.buildSeconds1200
timeouts.attemptSecondsrecorded 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)

Environment variables

VariableUsed for
BUGCAPSULE_URL, BUGCAPSULE_SECRET_KEY, BUGCAPSULE_INGEST_KEYCredentials (override ~/.bugcapsule/config.json)
BUGCAPSULE_ANDROID_GRADLE_ARGS, ANDROID_HOME, ANDROID_SDK_ROOTAndroid launcher
BUGCAPSULE_IOS_BUILD_SETTINGSiOS launcher
NO_COLORPlain output
BUGCAPSULE_GIT_SHA, BUGCAPSULE_GIT_DIRTY, BUGCAPSULE_REPLAY_URLSet by the CLI for the app being built or replayed