bug-bash
GitHub在隔离沙箱中运行 AI 探索代理对 Xum Web UI 进行自动化 bug bash,通过 e2e explore charter 驱动 Chromium 测试,并验证每个发现以生成失败的复现测试。
Trigger Scenarios
Install
npx skills add coder/xum --skill bug-bash -g -y
SKILL.md
Frontmatter
{
"name": "bug-bash",
"description": "Run an agent bug bash of the Xum web UI in the bug-bash sandbox (TesterArmy `e2e explore` charters, each in its own locked-down Docker container with a seeded `xum server`), then verify each finding with a failing repro test. Use when asked to \"bug bash\" Xum, run e2e explore agents or the MCP Apps suite against the app, or try to break the app with AI explorers."
}
Bug bash in the sandbox
Each charter is one e2e explore run: an AI explorer drives Chromium against its own seeded
xum server and reports findings. Every model-driven run happens inside the bug-bash sandbox
(#5714), a Docker container per job. Findings are claims. Report a bug only after a repro test
fails for the reported reason.
The generic method (charters, triage, verify, report) is upstream's guide:
node_modules/.bin/e2e guide bug-bash (it needs Node.js 22.22.3+ or 24.8+). This skill adds the
Xum harness. Where the two differ, follow this skill.
How the sandbox works
- The host pause (
tests/bugbash/hostPause.ts) refuses every model-driven e2e process on the host with exit 2:e2e explore, and every command withe2e.mcpapps.config.ts. Outside the sandbox the agents hold no model, so anagent.*step fails withMODEL_UNAVAILABLE. No override exists.e2e runande2e listof exact-step repros still run on the host. make bug-bash(tests/bugbash/run.ts) andmake mcp-apps-e2eare the launchers. They runtests/bugbash/sandbox/launch.ts, one container per job.- The image
ghcr.io/coder/xum-bugbash-sandbox(about 1.4 GB, linux/amd64) holds only tools: Node 22, Bun, git and the Chromium of@e2e-dev/web's playwright-core. The runner pulls it only by the digest intests/bugbash/sandbox/image.json. It builds it only in the case below. - The inputs key (
make bugbash-sandbox-key) is a hash of the Dockerfile,build.shand the playwright-core version inbun.lock. The runner refuses an image whose key differs from the checkout. One exception: withBUGBASH_SANDBOX_BUILD_STALE=1(exactly1), which only CI'sTest / Bug-bash sandboxjob sets, the self-check and the exact-step repros build the image locally withbuild.sh(local tag, no push, no registry login), check its inputs label, and print a warning (a::warning::annotation in GitHub Actions). Explore charters, the MCP Apps suite and the preflight ofmake bug-bashandmake mcp-apps-e2ealways refuse a stale key. - An image change lands in three steps: the PR that changes the Dockerfile,
build.shor playwright-core passes CI on a local build. After it merges, a maintainer runs the manual workflow "Bug-bash sandbox image" on main. Then a reviewed PR updatesimage.jsonto pin the new digest. An e2e update that keeps playwright-core keeps the key, so the image stays valid. - The container gets
--network none,--read-only,--cap-drop ALL, no-new-privileges, a non-root user, 4 GB memory and tmpfs scratch folders. It reads copies of the git-listed files, plusdist/andnode_modules/read-only. An env allowlist passes no credential. - One provider proxy per job (
sandbox/proxy.ts) runs in the launcher on the host and holds the API key. The container reaches it only through a read-only unix socket. The proxy allows onlyPOST /anthropic/v1/messagesto the job's models, clampsmax_tokens, and reserves each call's worst-case cost from the run's ledger before it sends the call. It writes one record per call to<output>.proxy.jsonl. - The container writes nothing on the host. At the end,
sandbox/entry.tssends the output folder on stdout as framed files (sandbox/exportStream.ts). The host writes only new regular files and stops at the first bad frame. - The container's stdin is the lifeline: when the launcher dies, the container stops.
1. Prepare
- Host: Linux on amd64, not root, with a local rootful Docker socket. Docker Desktop, rootless
Docker and a remote endpoint refuse (exit 2). The host needs git, make and the Bun version of
packageManagerinpackage.json(bun --version):makebuilds with Bun and runs the launchers with it. It needs no Node.js or Chromium for the sandbox targets. On a fresh checkout, runbun install. - Env:
ANTHROPIC_API_KEY,ANTHROPIC_BASE_URL(with or without/v1) andBUGBASH_BUDGET_USD. The runner reads the key only fromANTHROPIC_API_KEY: copy a key from another variable (for exampleANTHROPIC_AUTH_TOKEN) there. BUGBASH_BUDGET_USDcaps the whole run (every job and the app-AI probe) at list price. It is not a billing cap: the key's own limit is the backstop.- Dry run at $0 with no key:
make bug-bash-sandbox-check(isolation, proxy and export refusals against a fake upstream, about 2 minutes) andmake test-bugbash-repros-sandbox(the repros through the launcher on the mock app AI, about 4 minutes). CI'sTest / Bug-bash sandboxjob runs both.
2. Plan charters
Edit tests/bugbash/charters.txt, one slug|target|agent|charter per line. One area and one
persona per charter. For a branch, base the charters on git diff --stat origin/main...HEAD, or
pass a branch-specific file with --charters <file>.
- Targets (
tests/bugbash/e2e.config.ts):web1440x900,phone390x844. - Personas (
--agent):newcomer,keyboard,fuzzer,state,skeptic, or e2e's owndefault. - Name the workspace ("Bug bash playground") and the starting view in the goal.
- Model-driven jobs turn terminals off, so no charter can use a Terminal tab. Settings can still add stdio MCP servers and custom editor commands: keep explorers out of those.
3. Run
make bug-bash BUGBASH_BUDGET_USD=9 # 6 charters x Sonnet 5.5
make bug-bash BUGBASH_BUDGET_USD=3 BUGBASH_ARGS="--only composer" # one charter
- The run checks the env, Docker and the image first (about a second once the image is cached), then builds, then launches the jobs. The first run pulls the image, which takes minutes.
- Before any job it prints its estimate and a recommended budget, and refuses a lower budget with the number to set. The default needs $9 and takes about 5 minutes after the build.
- The app AI (
tests/bugbash/aiMode.ts):BUGBASH_AI=auto(default) probesBUGBASH_APP_MODEL(defaultanthropic:claude-haiku-5-5) once through a proxy. If the probe answers, the app uses that model through each job's proxy. If the upstream is busy (429 or 5xx), the app uses the mock. A rejected key (401 or 403) or an unreachable base URL refuses before any job.realnever falls back, andmocknever calls a provider. Charters that name a[mock:...]prompt always use the mock.
Options:
BUGBASH_ARGS:--only <slug,...>,--charters <file>,--parallel N(default 8, all models together),--max-steps N(1-12, default 6),--config tests/bugbash/e2e.mcpapps.config.ts(the only other config that runs).BUGBASH_MODELS: comma-separatedanthropic:<model>, default Sonnet 5.5. Only models with a price intests/bugbash/sandbox/proxyPolicy.tsrun (claude-sonnet-5-5,claude-opus-5-5,claude-haiku-5-5). Adding Opus finds more distinct bugs and needs about $29.BUGBASH_EFFORT: low, medium (default), high, xhigh or max. A higher effort raises the estimate as a marked guess.make bug-bashrefusesBUGBASH_MODEL, which onlymake mcp-apps-e2ereads.- Every model-driven job refuses
BUGBASH_SCENARIO: itsbash-ai-proxyscenario needs project automation, which those jobs turn off.
MCP Apps: make mcp-apps-e2e BUGBASH_BUDGET_USD=5 runs tests/bugbash/mcpapps/mcp-apps.e2e.ts
(7 tests on web and phone) with BUGBASH_MODEL (default Sonnet 5.5). It pins the mock app AI,
because its seed writes the MCP chat directly. Measured runs spent $0.93 to $1.71 and took 8 to 10
minutes. MCP_APPS_E2E_ARGS passes e2e arguments. To explore it, use
BUGBASH_ARGS="--config tests/bugbash/e2e.mcpapps.config.ts --charters tests/bugbash/mcpapps/charters.txt".
Suite tests drive flows with agent.act in on-screen words and pin each outcome with an exact
expect. Use agent.assert(..., { vision: "only" }) for "is it on screen": the accessibility
tree also lists off-screen nodes. Prove a new suite test fails on the old dist/ before the fix
lands.
4. Read the results
The run folder is tests/bugbash/.e2e/bugbash/<run>/ (git-ignored):
findings.md: every charter with its exit code, how it ended, the app AI mode, the cost and the findings. Merge findings that two models report about the same defect.<model>/<slug>.log: the sandbox log.<model>/<slug>.proxy.jsonl: one record per model call.<model>/<slug>/:summary.md,report.json,app.logandresults/(screenshots, video).
Exit codes of make bug-bash:
| Code | Meaning |
|---|---|
| 0 | Every charter ran. Read findings.md: a charter with issues exits 1 in its table. |
| 1 | A charter ran no step (the Ended column tells this apart from issues). |
| 2 | Refused. Stderr and the charter log name the reason. |
| 3 | A container's state is unknown (see Leftovers). |
| 4 | Incomplete evidence, or an internal error of e2e or the launcher. |
| 5 | A proxy fault (a call cost more than its bound, or a record was not written). |
| 6 | e2e's own infrastructure error. |
| 130, 143 | Stopped. |
A stop or a proxy fault before the jobs start writes no run folder.
Leftovers: every launch lists the job containers of its checkout. make bug-bash-sandbox-recover
removes only those whose launcher is provably dead on this boot, and exits 3 when it cannot tell
for one. Check that container's xum.bugbash.owner and xum.bugbash.checkout labels with
docker inspect, then docker rm -f <id> it. A SIGKILLed launcher also leaves
$TMPDIR/xum-bugbash-sandbox/<checkout>/<job> and a xum-bugbash-docker-* folder in $TMPDIR:
remove both by hand. A run with BUGBASH_SANDBOX_BUILD_STALE=1 on a stale key leaves a local
image of about 1.4 GB: remove it with docker image rm xum-bugbash-sandbox:inputs-<key>.
5. Triage and verify
Follow the sections "5. Triage", "6. Verify" and "7. Report" of e2e guide bug-bash, with these
Xum rules:
- Check
findings.mdfor the charter's app AI mode. In mock mode, "Mock response: ..." replies and "model calls are disabled in mock AI mode" errors are expected, not bugs. - Read the source (
src/browser/...) before keeping a finding. Reject explorer mistakes first. - A repro goes in
tests/bugbash/repros/<slug>.e2e.ts(tracked) with exactscreen,browserandexpectsteps, neveragent.*.tests/bugbash/reproRules.test.tsfails on a repro that takes theagentfixture. - Prove every repro: put the bug back in a scratch edit, rebuild, see
ASSERTION_FAILEDon the bug's own assertion, then remove the edit and see it pass. - Tags:
bugbashplus the bug ID. Addmock-onlywhen the test needs a[mock:...]prompt or a terminal. Addknown-failureplus the issue number for an open bug, and remove it in the PR that fixes the bug. - Run repros on the host with
make test-bugbash-repros(Node 22.22.3+ or 24.8+ asE2E_NODE, andnode_modules/.bin/e2e-web install chromiumonce). One repro:make test-bugbash-repros BUGBASH_REPRO_ARGS="repros/<slug>.e2e.ts". With a key set, the non-mock repros make paid app-model calls: useBUGBASH_AI=mockfor none. A rejected key fails the target, andmake test-e2etoo, even after Playwright passed: a mock run must not hide a broken setup.BUGBASH_REPROS=0skips the repro phase ofmake test-e2e. Run e2e through the make targets:e2e.config.tsrefuses an unresolved app AI mode unlessBUGBASH_AI=mock. - After fixing findings, rerun the bug bash on the changed behaviors until a round finds no regression, incomplete fix or new defect in changed code. File unrelated older bugs as issues.
Report confirmed bugs first (title, expected vs actual, root cause file:line, steps, video path,
repro test), then unverified risks, then rejected findings by reason, then the charters run and
their cost.
Version History
- 5cedf41 Current 2026-10-19 19:24


