Replay a capsule
twin replay rebuilds a capsule’s environment on a fresh Solari sandbox, runs the command, and reports whether the failure reproduces. The sandbox gets the same runtime, package manager and lockfile, the same commit and diff, and the same environment values and time zone.
Before you start
Section titled “Before you start”-
Node 22 or newer.
-
A Solari key. Get one at console.getsolari.com:
export SOLARI_API_KEY=slr_live_... -
The capsule, as a file path or an
https://URL such as the issue attachment link.twin inspectshows what it contains and needs no key:twin inspect twin-capsule.jsontwin inspect https://github.com/user-attachments/files/<id>/twin-capsule.json
Run it
Section titled “Run it”twin replay twin-capsule.jsonProgress goes to stderr as each step starts. The report goes to stdout. The echarts replay, recorded on Solari:
Replay on solari (machine AlDq8esMs1x4vuzv…, run fd0c959a) ✓ prepare workspace 0.3s ✓ install node 22.23.3 2.1s ✓ check out 984bf46b2f90 4.5s ✓ install npm 10.9.9 3.7s ✓ install dependencies (npm ci) 46.4s
Attempts 1 FAIL exit 1 exit1:0aee0834ac9c4d2d 8.0s 2 FAIL exit 1 exit1:0aee0834ac9c4d2d 1.7s expected FAIL exit1:0aee0834ac9c4d2d
REPRODUCED: every attempt failed exactly as the capsule recorded.That run used two attempts; the default is three.
Reading the verdict
Section titled “Reading the verdict”| Verdict | Meaning | What to do next |
|---|---|---|
REPRODUCED |
Every attempt failed exactly as the capsule recorded. | Keep the machine (--keep) and open a shell, bisect against a passing capsule, or verify a fix. |
DIFFERENT FAILURE |
It fails here, but not the way the capsule recorded. | Compare the “Key lines here” and “Key lines in capsule” sections of the report. The usual causes are a missing environment value (see Notes) or a runtime that formats the error differently. |
NOT REPRODUCED |
The command passes on this Linux machine. | Replay cannot copy the OS, variables recorded by name only, untracked files or packages outside npm. Check the Notes, supply values with --env, or run twin bisect against a passing capsule. |
FLAKY |
Attempts disagreed with each other. | Raise --attempts to see how often it fails. |
INCONCLUSIVE |
Setup failed or an attempt timed out. | The failed step is marked ✗ with its exit code and the last 30 lines of its output. See Troubleshooting. |
After the verdict, twin lists the differences between the reporter’s machine and the replay that it could not remove. Examples, with names and counts that vary:
Captured on darwin (macos); replayed on Linux with the same versions.3 variables were set on the reporter's machine with unknown values and are left unset (API_URL, DATABASE_URL, SENTRY_DSN). Provide any that matter with --env NAME=value.2 untracked files existed on the reporter's machine (names only, not recreated).The diff was scrubbed for secrets and may not apply cleanly.Overridden with --env, so this replay differs from the capsule in: TZ.
Pinned packages
Section titled “Pinned packages”Replay installs dependencies from the repository’s lockfile. A failure can still come from versions the reporter’s install resolved differently, such as a fresh install that picked a newer transitive release. So for npm projects, replay lists the installed packages after the install, compares them with the versions the capsule recorded, and installs the ones that differ in one extra step:
✓ install dependencies (npm ci) 9.7s ✓ pin 32 packages to the capsule's versions (@asamuzakjp/css-color@5.1.5, ...) 5.7sOnly packages installed at other versions are pinned. A package missing from the machine is left alone, since that is usually a platform or optional difference. If a package has several new versions recorded, there is no single version to install, so it is skipped and named in the Notes. When nothing differs there is no extra step. Other package managers are unchanged, and --ref runs do not pin because the capsule’s packages describe another commit.
If a pinned version cannot be installed, the pin step fails and the verdict is INCONCLUSIVE. --no-pin skips the step, and --json lists the packages under pinned.
The lru-cache case study only reproduces with pinning: 32 packages differ from the lockfile install, and replay reproduces the failure 3 of 3.
Options
Section titled “Options”| Option | Default | What it does |
|---|---|---|
--attempts <n> |
3 |
Runs of the command. More than one separates reproducible failures from flaky ones. |
--keep |
off | Keep the machine running at the failure when the verdict is REPRODUCED (then: twin shell). |
--repo <url> |
the capsule’s remote | Clone from here instead: a fork, a mirror, or a remote the capsule does not name. |
--ref <sha> |
the capsule’s commit | Check out this commit instead. Skips the capsule’s working-tree diff and the package pinning. |
--no-pin |
off | Do not install the capsule’s recorded npm package versions over the lockfile install (see Pinned packages). |
--env <NAME=value> |
none | Value for a variable the capsule recorded by name only. Repeatable. Also overrides recorded values. |
--timeout <minutes> |
15 |
Maximum time per attempt. An attempt that hits it is killed and the verdict is INCONCLUSIVE. |
-v, --verbose |
off | Stream the sandbox’s output (setup and command) to stderr. |
--json |
off | Machine-readable report on stdout. |
Exit status: 0 reproduced, 1 anything else, 2 usage error.
Supplying values with --env
Section titled “Supplying values with --env”A capsule records most variables by name only. If the Notes say a variable was set with an unknown value and you think it matters, give it one:
twin replay twin-capsule.json --env API_URL=http://localhost:3000 --env FEATURE_X=1--env also tests a hypothesis. A time-zone capsule replays as REPRODUCED, and the same capsule with --env TZ=UTC is NOT REPRODUCED.
Replaying a fork or a different commit
Section titled “Replaying a fork or a different commit”The sandbox fetches one commit by SHA from the capsule’s origin remote. If the reporter’s commit only exists on their fork:
twin replay twin-capsule.json --repo https://github.com/reporter/projectTo replay the captured environment against another commit, such as current main, pass its full SHA with --ref. The commit is fetched by SHA, which GitHub and GitLab allow for full SHAs. The capsule’s uncommitted diff is not applied, since it was made against the captured commit.
twin replay twin-capsule.json --ref "$(git rev-parse origin/main)"Keeping the machine
Section titled “Keeping the machine”twin replay twin-capsule.json --keepWhen the verdict is REPRODUCED, the machine stays up and the report ends with:
The machine is still running at the failure, in the reporter's environment: twin shell <id> open a terminal on it twin shell <id> --web get a browser terminal link to shareIt is billed while running and released after 15 minutes idle, or now with: twin stopFor any other verdict the machine is released as usual. See Shell.
JSON output
Section titled “JSON output”--json prints the full report on stdout: verdict (reproduced, different-failure, not-reproduced, flaky or inconclusive), runId, backend, machineId, kept, expected, steps, attempts, pinned and notes. Progress stays on stderr, so stdout can be piped.