WonderTwin in GitHub Actions
WonderTwin in GitHub Actions
Section titled “WonderTwin in GitHub Actions”Run a twin against your test suite in a GitHub Actions workflow. Capture replay artifacts on every run, including failures.
Snippet
Section titled “Snippet”Drop this into .github/workflows/test.yml (or merge into your existing workflow):
name: Teston: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
# 1. Install wt CLI - name: Install wt run: | curl -sSL "https://github.com/wondertwin-ai/wondertwin/releases/latest/download/wt-linux-amd64" -o /usr/local/bin/wt chmod +x /usr/local/bin/wt
# 2. Install license (soft-gate: optional but recommended) - name: Install WonderTwin license if: env.WONDERTWIN_LICENSE != '' env: WONDERTWIN_LICENSE: ${{ secrets.WONDERTWIN_LICENSE }} run: | printf '%s' "$WONDERTWIN_LICENSE" > "$RUNNER_TEMP/license.json" wt license install "$RUNNER_TEMP/license.json" rm "$RUNNER_TEMP/license.json"
# 3. Install + start the twin - name: Start twin-<TWIN> run: | wt install <TWIN>@<TWIN_VERSION> wt up > "$RUNNER_TEMP/twin.log" 2>&1 & echo "TWIN_PID=$!" >> "$GITHUB_ENV" # health check for i in {1..30}; do curl -sf http://127.0.0.1:<TWIN_PORT>/admin/state > /dev/null && break sleep 0.2 done
# 4. Start a run - name: Start run env: CI: "true" run: | wt runs start \ --twin http://127.0.0.1:<TWIN_PORT> \ --seed 42 \ --run-id "${{ github.run_id }}-${{ github.run_attempt }}"
# 5. Run your tests — they hit $WT_TWIN_URL - name: Test run: <TEST_COMMAND>
# 6. Finish the run + export — runs on success AND failure - name: Finish run + export replay if: always() run: | wt runs finish \ --twin http://127.0.0.1:<TWIN_PORT> \ --run-id "${{ github.run_id }}-${{ github.run_attempt }}" \ --export "$RUNNER_TEMP/wondertwin-replay.jsonl.gz"
# 7. Upload the replay as a build artifact — runs on success AND failure - name: Upload replay if: always() uses: actions/upload-artifact@v4 with: name: wondertwin-replay path: ${{ runner.temp }}/wondertwin-replay.jsonl.gz retention-days: 14
# 8. Cleanup - name: Stop twin if: always() run: | if [ -n "$TWIN_PID" ]; then kill "$TWIN_PID" 2>/dev/null || true sleep 2 kill -9 "$TWIN_PID" 2>/dev/null || true fiSetup checklist
Section titled “Setup checklist”- Pick your twin and version. Replace
<TWIN>(e.g.stripe,twilio,posthog) and<TWIN_VERSION>(e.g.0.1.0orlatest). Replace<TWIN_PORT>with the twin’s default port (twin-stripe:4111; check the twin’s README for others). - Replace
<TEST_COMMAND>with your actual test invocation (npm test,go test ./...,pytest, etc.). - Set the license secret. In your GitHub repo: Settings → Secrets and variables → Actions → New repository secret. Name:
WONDERTWIN_LICENSE. Value: the full contents of the license JSON file (paste it as-is; do not base64-encode).- If you don’t have a license yet, the workflow runs anyway with a soft-gate warning in the logs. Replay artifacts still produced.
- To get a license, contact sales (link forthcoming).
- Tell your test code where the twin is. Most SDKs accept a base URL override:
- Stripe:
Stripe.api_base = ENV['WT_TWIN_URL'] || ENV['TWIN_URL'] - Or set the SDK’s environment variable that controls the base URL
- The action exports both
WT_TWIN_URLandTWIN_URLto subsequent steps.
- Stripe:
- Adjust
retention-daysto match your team’s needs. 14 is a safe default for debugging; longer retention costs more GitHub Actions storage.
Variations
Section titled “Variations”With BYO synthetic fixtures
Section titled “With BYO synthetic fixtures”Pass a --fixtures file to seed the twin with realistic data:
- name: Start run with fixtures run: | wt runs start \ --twin http://127.0.0.1:4111 \ --seed 42 \ --fixtures fixtures/stripe-snapshot.json \ --run-id "${{ github.run_id }}-${{ github.run_attempt }}"The fixtures file must conform to the twin’s snapshot schema. See concepts.md on BYO synthetic data.
Multiple twins in one job
Section titled “Multiple twins in one job”Each twin needs its own port. Run two wt up invocations against separate manifest files, or split twins across jobs.
- name: Start twin-stripe run: | wt install stripe@0.1.0 PORT=4111 wt up > $RUNNER_TEMP/stripe.log 2>&1 & echo "STRIPE_PID=$!" >> $GITHUB_ENV- name: Start twin-twilio run: | wt install twilio@0.1.0 PORT=4112 wt up > $RUNNER_TEMP/twilio.log 2>&1 & echo "TWILIO_PID=$!" >> $GITHUB_ENVThe simpler pattern is one twin per job. Multi-twin in one job is supported but you own the port management.
Without an explicit run lifecycle (using wt runs wrap)
Section titled “Without an explicit run lifecycle (using wt runs wrap)”If your test command is a single shell invocation, you can collapse start + finish into one step:
- name: Test with wrap run: | wt runs wrap \ --twin http://127.0.0.1:4111 \ --seed 42 \ --run-id "${{ github.run_id }}-${{ github.run_attempt }}" \ --export "$RUNNER_TEMP/wondertwin-replay.jsonl.gz" \ -- <TEST_COMMAND>wrap always finishes the run on exit (success, failure, or signal). Less customizable than the explicit lifecycle; cleaner for simple test commands.
Pin both wt and the twin version
Section titled “Pin both wt and the twin version”Pin to specific versions to make CI runs reproducible across time:
- run: | curl -sSL "https://github.com/wondertwin-ai/wondertwin/releases/download/v0.4.2/wt-linux-amd64" -o /usr/local/bin/wt chmod +x /usr/local/bin/wt- run: wt install stripe@0.1.0Troubleshooting
Section titled “Troubleshooting”My replay artifact is empty / truncated when the test step fails
Section titled “My replay artifact is empty / truncated when the test step fails”The shutdown path needs grace. If your test step is killed by a workflow timeout (rather than exiting normally), the wt runs finish step may not have time to drain the telemetry/replay buffer.
The if: always() guards on the finish + upload steps cover most cases. If you’re hitting hard timeouts, increase the workflow’s timeout-minutes to give the cleanup steps room.
See concepts.md on shutdown grace.
The twin doesn’t come up in time
Section titled “The twin doesn’t come up in time”The health check loop is 30 polls × 200ms = 6 seconds. For complex twins or slow CI runners, increase the loop count or the sleep interval. If the twin reliably takes longer than that to start, file an issue — that’s a regression we want to know about.
My test code can’t find the twin
Section titled “My test code can’t find the twin”Confirm WT_TWIN_URL and TWIN_URL are visible to your test code. Some test runners scrub env vars; if so, pass the URL explicitly:
- name: Test env: STRIPE_API_BASE: http://127.0.0.1:4111 run: npm testLicense doesn’t validate
Section titled “License doesn’t validate”Three common causes:
- The secret was base64-encoded when pasted — paste the JSON as-is.
- The license expired — check
wt license status. - The license
twin_scopedoesn’t include the twin you’re running — check the JSON, scope should be["*"]or include your twin name.
I’m running self-hosted GitHub Actions runners
Section titled “I’m running self-hosted GitHub Actions runners”The snippet works as-is. If your runners don’t have curl installed, replace the wt install step with whatever your image provides (wget, pre-baking the binary, etc.).
Reference
Section titled “Reference”wt runs --helpfor full CLI optionsconcepts.md— twins, runs, replay, licensestrategy.md— why docs first- Open issues / requests