OpenClaw QA Testing
Use this skill for qa-lab / qa-channel work. Repo-local QA only.
Read first
docs/concepts/qa-e2e-automation.md
docs/help/testing.md
docs/channels/qa-channel.md
qa/README.md
qa/scenarios/index.yaml
extensions/qa-lab/src/suite.ts
extensions/qa-lab/src/character-eval.ts
Model policy
- Live OpenAI lane:
openai/gpt-5.4
- Fast mode: on
- Do not use:
openai/gpt-5.4-pro
openai/gpt-5.4-mini
- Only change model policy if the user explicitly asks.
Default workflow
- Read the scenario pack and current suite implementation.
- Decide lane:
- mock/dev:
mock-openai
- real validation:
live-frontier
- For live OpenAI, use:
OPENCLAW_LIVE_OPENAI_KEY="${OPENAI_API_KEY}" \
pnpm openclaw qa suite \
--provider-mode live-frontier \
--model openai/gpt-5.4 \
--alt-model openai/gpt-5.4 \
--output-dir .artifacts/qa-e2e/run-all-live-frontier-<tag>
- Watch outputs:
- summary:
.artifacts/qa-e2e/run-all-live-frontier-<tag>/qa-suite-summary.json
- report:
.artifacts/qa-e2e/run-all-live-frontier-<tag>/qa-suite-report.md
- If the user wants to watch the live UI, find the current
openclaw-qa listen port and report http://127.0.0.1:<port>.
- If a scenario fails, fix the product or harness root cause, then rerun the full lane.
OTEL smoke
For local QA-lab OpenTelemetry validation, use:
pnpm qa:otel:smoke
This starts a local OTLP/HTTP trace receiver, runs the otel-trace-smoke
scenario through qa-channel, decodes the emitted protobuf spans, and verifies
the exported trace names and privacy contract. It does not require Opik,
Langfuse, or external collector credentials.
QA credentials and 1Password
- Use
op only inside tmux for QA secret lookup in this repo.
- Quick auth check inside tmux:
op account list
- Direct Telegram npm live test secrets currently live in 1Password item:
- vault:
OpenClaw
- item:
Telegram E2E
- That item is the first place to look for:
OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN
OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN
OPENCLAW_QA_PROVIDER_MODE
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC
- Convex QA secrets currently live in 1Password items:
- vault:
OpenClaw
- item:
OPENCLAW_QA_CONVEX_SITE_URL
- item:
OPENCLAW_QA_CONVEX_SECRET_MAINTAINER
- item:
OPENCLAW_QA_CONVEX_SECRET_CI
- Additional related notes/login items seen during QA credential work:
- vault:
Private
- items:
OPENCLAW QA, Convex, Telegram
- If a required value is missing from those notes:
- do not guess
- ask the maintainer/operator for the current value or the current 1Password item name
- for Telegram direct runs,
OPENCLAW_QA_TELEGRAM_GROUP_ID may be stored separately from Telegram E2E
- for Convex runs, the leased Telegram credential should provide the Telegram group id and bot tokens together; do not require a separate
OPENCLAW_QA_TELEGRAM_GROUP_ID
- for Convex runs, prefer
OpenClaw/OPENCLAW_QA_CONVEX_SITE_URL; if that is stale or unclear, ask for the active pool URL before running
- Prefer direct Telegram envs for the npm Telegram Docker lane when available:
OPENCLAW_QA_TELEGRAM_GROUP_ID="..." \
OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN="..." \
OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN="..." \
OPENCLAW_QA_PROVIDER_MODE="mock-openai" \
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC="openclaw@beta" \
pnpm test:docker:npm-telegram-live
- Prefer Convex mode when the goal is stable shared QA infra:
- round-robin credential leasing
- thinner wrapper for channel-specific setup
- CLI/admin flows around the pooled credentials
- Live npm Telegram Docker lane note:
scripts/e2e/npm-telegram-live-runner.ts reads OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE
- do not assume
OPENCLAW_QA_PROVIDER_MODE is consumed by that wrapper
- if a 1Password note only gives
OPENCLAW_QA_PROVIDER_MODE, map it explicitly to OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE before running the Docker lane
- Verified live shape:
- Convex mode can pass the real Docker lane without direct Telegram env vars
- leased Telegram payload includes the group id coupled to the driver/SUT tokens
- a real run of
pnpm test:docker:npm-telegram-live passed with:
OPENCLAW_QA_CREDENTIAL_SOURCE=convex
OPENCLAW_QA_CREDENTIAL_ROLE=maintainer
OPENCLAW_QA_CONVEX_SITE_URL
OPENCLAW_QA_CONVEX_SECRET_MAINTAINER
OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE=mock-openai
- If direct Telegram env is missing locally and
op signin blocks, prefer dispatching the manual GitHub lane because the qa-live-shared environment already has Convex CI credentials:
gh workflow run "NPM Telegram Beta E2E" --repo openclaw/openclaw --ref main \
-f package_spec=openclaw@YYYY.M.D-beta.N \
-f package_label=openclaw@YYYY.M.D-beta.N \
-f provider_mode=mock-openai
- Poll the exact run id from the dispatch URL.
gh run view --json artifacts is not supported; list artifacts with:
gh api repos/openclaw/openclaw/actions/runs/<run-id>/artifacts
WhatsApp live credentials
Use this when setting up or replacing Convex kind=whatsapp credentials.
- Treat WhatsApp QA credentials as operator-owned live accounts, not generated fixtures.
- Use two dedicated WhatsApp-capable test numbers: one driver account and one SUT account. Do not use personal numbers or personal OpenClaw WhatsApp accounts in the shared pool.
- Register and link each account manually with WhatsApp or WhatsApp Business, storing Web auth only in isolated local auth dirs outside the repo.
- For group coverage, create a dedicated test group that includes both QA accounts and store its JID as
groupJid; otherwise the group mention-gating scenario should be skipped by default and fail when explicitly requested.
- Package the two Baileys auth dirs into base64
.tgz payload fields and add a new active Convex credential row. Prefer adding a fresh row and disabling stale/broken rows over overwriting credentials in place.
- Expected payload fields:
driverPhoneE164, sutPhoneE164, driverAuthArchiveBase64, sutAuthArchiveBase64, and optional groupJid.
- Keep credential material out of the repo, logs, PRs, and screenshots. Redact phone numbers unless the operator explicitly asks for local debugging.
- Validate with
pnpm openclaw qa whatsapp --credential-source convex --credential-role maintainer --provider-mode mock-openai and preserve artifact paths plus redacted pass/fail summaries.
- If WhatsApp expires or invalidates a linked Web session, relink locally, package fresh auth archives, add a new Convex row, then disable the stale row.
Character evals
Use qa character-eval for style/persona/vibe checks across multiple live models.
pnpm openclaw qa character-eval \
--model openai/gpt-5.4,thinking=xhigh \
--model openai/gpt-5.2,thinking=xhigh \
--model openai/gpt-5,thinking=xhigh \
--model anthropic/claude-opus-4-6,thinking=high \
--model anthropic/claude-sonnet-4-6,thinking=high \
--model zai/glm-5.1,thinking=high \
--model moonshot/kimi-k2.5,thinking=high \
--model google/gemini-3.1-pro-preview,thinking=high \
--judge-model openai/gpt-5.4,thinking=xhigh,fast \
--judge-model anthropic/claude-opus-4-6,thinking=high \
--concurrency 16 \
--judge-concurrency 16 \
--output-dir .artifacts/qa-e2e/character-eval-<tag>
- Runs local QA gateway child processes, not Docker.
- Preferred model spec syntax is
provider/model,thinking=<level>[,fast|,no-fast|,fast=<bool>] for both --model and --judge-model.
- Do not add new examples with separate
--model-thinking; keep that flag as legacy compatibility only.
- Defaults to candidate models
openai/gpt-5.4, openai/gpt-5.2, openai/gpt-5, anthropic/claude-opus-4-6, anthropic/claude-sonnet-4-6, zai/glm-5.1, moonshot/kimi-k2.5, and google/gemini-3.1-pro-preview when no --model is passed.
- Candidate thinking defaults to
high, with xhigh for OpenAI models that support it. Prefer inline --model provider/model,thinking=<level>; --thinking <level> and --model-thinking <provider/model=level> remain compatibility shims.
- OpenAI candidate refs default to fast mode so priority processing is used where supported. Use inline
,fast, ,no-fast, or ,fast=false for one model; use --fast only to force fast mode for every candidate.
- Judges default to
openai/gpt-5.4,thinking=xhigh,fast and anthropic/claude-opus-4-6,thinking=high.
- Report includes judge ranking, run stats, durations, and full transcripts; do not include raw judge replies. Duration is benchmark context, not a grading signal.
- Candidate and judge concurrency default to 16. Use
--concurrency <n> and --judge-concurrency <n> to override when local gateways or provider limits need a gentler lane.
- Scenario source is YAML-only under
qa/scenarios/: use index.yaml and
per-scenario *.yaml files with top-level title, scenario, and optional
flow. Never add fenced qa-scenario / qa-flow Markdown files.
- For isolated character/persona evals, write the persona into
SOUL.md and blank IDENTITY.md in the scenario flow. Use SOUL.md + IDENTITY.md only when intentionally testing how the normal OpenClaw identity combines with the character.
- Keep prompts natural and task-shaped. The candidate model should receive character setup through
SOUL.md, then normal user turns such as chat, workspace help, and small file tasks; do not ask "how would you react?" or tell the model it is in an eval.
- Prefer at least one real task, such as creating or editing a tiny workspace artifact, so the transcript captures character under normal tool use instead of pure roleplay.
Codex CLI model lane
Use model refs shaped like codex-cli/<codex-model> whenever QA should exercise Codex as a model backend.
Examples:
pnpm openclaw qa suite \
--provider-mode live-frontier \
--model codex-cli/<codex-model> \
--alt-model codex-cli/<codex-model> \
--scenario <scenario-id> \
--output-dir .artifacts/qa-e2e/codex-<tag>
pnpm openclaw qa manual \
--model codex-cli/<codex-model> \
--message "Reply exactly: CODEX_OK"
- Treat the concrete Codex model name as user/config input; do not hardcode it in source, docs examples, or scenarios.
- Live QA preserves
CODEX_HOME so Codex CLI auth/config works while keeping HOME and OPENCLAW_HOME sandboxed.
- Mock QA should scrub
CODEX_HOME.
- If Codex returns fallback/auth text every turn, first check
CODEX_HOME,
relevant secret-backed auth, and gateway child logs before changing
scenario assertions.
- For model comparison, include
codex-cli/<codex-model> as another candidate in qa character-eval; the report should label it as an opaque model name.
Repo facts
- Seed scenarios live in
qa/scenarios/index.yaml and
qa/scenarios/<theme>/*.yaml.
- Main live runner:
extensions/qa-lab/src/suite.ts
- QA lab server:
extensions/qa-lab/src/lab-server.ts
- Child gateway harness:
extensions/qa-lab/src/gateway-child.ts
- Synthetic channel:
extensions/qa-channel/
What “done” looks like
- Full suite green for the requested lane.
- User gets:
- watch URL if applicable
- pass/fail counts
- artifact paths
- concise note on what was fixed
Common failure patterns
- Live timeout too short:
- widen live waits in
extensions/qa-lab/src/suite.ts
- Discovery cannot find repo files:
- point prompts at
repo/... inside seeded workspace
- Subagent proof too brittle:
- prefer stable final reply evidence over transient child-session listing
- Harness “rebuild” delay:
- dirty tree can trigger a pre-run build; expect that before ports appear
When adding scenarios
- Add or update scenario YAML under
qa/scenarios/; do not add .md scenario
files or fenced YAML blocks.
- Keep kickoff expectations in
qa/scenarios/index.yaml aligned
- Add executable coverage in
extensions/qa-lab/src/suite.ts
- Prefer end-to-end assertions over mock-only checks
- Save outputs under
.artifacts/qa-e2e/
1---2name: openclaw-qa-testing3description: Run, watch, debug, extend, or explain OpenClaw qa-lab and qa-channel scenarios, artifacts, and live lanes.4---56# OpenClaw QA Testing78Use this skill for `qa-lab` / `qa-channel` work. Repo-local QA only.910## Read first1112- `docs/concepts/qa-e2e-automation.md`13- `docs/help/testing.md`14- `docs/channels/qa-channel.md`15- `qa/README.md`16- `qa/scenarios/index.yaml`17- `extensions/qa-lab/src/suite.ts`18- `extensions/qa-lab/src/character-eval.ts`1920## Model policy2122- Live OpenAI lane: `openai/gpt-5.4`23- Fast mode: on24- Do not use:25 - `openai/gpt-5.4-pro`26 - `openai/gpt-5.4-mini`27- Only change model policy if the user explicitly asks.2829## Default workflow30311. Read the scenario pack and current suite implementation.322. Decide lane:33 - mock/dev: `mock-openai`34 - real validation: `live-frontier`353. For live OpenAI, use:3637```bash38OPENCLAW_LIVE_OPENAI_KEY="${OPENAI_API_KEY}" \39pnpm openclaw qa suite \40 --provider-mode live-frontier \41 --model openai/gpt-5.4 \42 --alt-model openai/gpt-5.4 \43 --output-dir .artifacts/qa-e2e/run-all-live-frontier-<tag>44```45464. Watch outputs:47 - summary: `.artifacts/qa-e2e/run-all-live-frontier-<tag>/qa-suite-summary.json`48 - report: `.artifacts/qa-e2e/run-all-live-frontier-<tag>/qa-suite-report.md`495. If the user wants to watch the live UI, find the current `openclaw-qa` listen port and report `http://127.0.0.1:<port>`.506. If a scenario fails, fix the product or harness root cause, then rerun the full lane.5152## OTEL smoke5354For local QA-lab OpenTelemetry validation, use:5556```bash57pnpm qa:otel:smoke58```5960This starts a local OTLP/HTTP trace receiver, runs the `otel-trace-smoke`61scenario through qa-channel, decodes the emitted protobuf spans, and verifies62the exported trace names and privacy contract. It does not require Opik,63Langfuse, or external collector credentials.6465## QA credentials and 1Password6667- Use `op` only inside `tmux` for QA secret lookup in this repo.68- Quick auth check inside tmux:6970```bash71op account list72```7374- Direct Telegram npm live test secrets currently live in 1Password item:75 - vault: `OpenClaw`76 - item: `Telegram E2E`77- That item is the first place to look for:78 - `OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN`79 - `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`80 - `OPENCLAW_QA_PROVIDER_MODE`81 - `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC`82- Convex QA secrets currently live in 1Password items:83 - vault: `OpenClaw`84 - item: `OPENCLAW_QA_CONVEX_SITE_URL`85 - item: `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`86 - item: `OPENCLAW_QA_CONVEX_SECRET_CI`87- Additional related notes/login items seen during QA credential work:88 - vault: `Private`89 - items: `OPENCLAW QA`, `Convex`, `Telegram`90- If a required value is missing from those notes:91 - do not guess92 - ask the maintainer/operator for the current value or the current 1Password item name93 - for Telegram direct runs, `OPENCLAW_QA_TELEGRAM_GROUP_ID` may be stored separately from `Telegram E2E`94 - for Convex runs, the leased Telegram credential should provide the Telegram group id and bot tokens together; do not require a separate `OPENCLAW_QA_TELEGRAM_GROUP_ID`95 - for Convex runs, prefer `OpenClaw/OPENCLAW_QA_CONVEX_SITE_URL`; if that is stale or unclear, ask for the active pool URL before running96- Prefer direct Telegram envs for the npm Telegram Docker lane when available:9798```bash99OPENCLAW_QA_TELEGRAM_GROUP_ID="..." \100OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN="..." \101OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN="..." \102OPENCLAW_QA_PROVIDER_MODE="mock-openai" \103OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC="openclaw@beta" \104pnpm test:docker:npm-telegram-live105```106107- Prefer Convex mode when the goal is stable shared QA infra:108 - round-robin credential leasing109 - thinner wrapper for channel-specific setup110 - CLI/admin flows around the pooled credentials111- Live npm Telegram Docker lane note:112 - `scripts/e2e/npm-telegram-live-runner.ts` reads `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE`113 - do not assume `OPENCLAW_QA_PROVIDER_MODE` is consumed by that wrapper114 - if a 1Password note only gives `OPENCLAW_QA_PROVIDER_MODE`, map it explicitly to `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE` before running the Docker lane115- Verified live shape:116 - Convex mode can pass the real Docker lane without direct Telegram env vars117 - leased Telegram payload includes the group id coupled to the driver/SUT tokens118 - a real run of `pnpm test:docker:npm-telegram-live` passed with:119 - `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`120 - `OPENCLAW_QA_CREDENTIAL_ROLE=maintainer`121 - `OPENCLAW_QA_CONVEX_SITE_URL`122 - `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`123 - `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE=mock-openai`124- If direct Telegram env is missing locally and `op signin` blocks, prefer dispatching the manual GitHub lane because the `qa-live-shared` environment already has Convex CI credentials:125126```bash127gh workflow run "NPM Telegram Beta E2E" --repo openclaw/openclaw --ref main \128 -f package_spec=openclaw@YYYY.M.D-beta.N \129 -f package_label=openclaw@YYYY.M.D-beta.N \130 -f provider_mode=mock-openai131```132133- Poll the exact run id from the dispatch URL. `gh run view --json artifacts` is not supported; list artifacts with:134135```bash136gh api repos/openclaw/openclaw/actions/runs/<run-id>/artifacts137```138139## WhatsApp live credentials140141Use this when setting up or replacing Convex `kind=whatsapp` credentials.142143- Treat WhatsApp QA credentials as operator-owned live accounts, not generated fixtures.144- Use two dedicated WhatsApp-capable test numbers: one driver account and one SUT account. Do not use personal numbers or personal OpenClaw WhatsApp accounts in the shared pool.145- Register and link each account manually with WhatsApp or WhatsApp Business, storing Web auth only in isolated local auth dirs outside the repo.146- For group coverage, create a dedicated test group that includes both QA accounts and store its JID as `groupJid`; otherwise the group mention-gating scenario should be skipped by default and fail when explicitly requested.147- Package the two Baileys auth dirs into base64 `.tgz` payload fields and add a new active Convex credential row. Prefer adding a fresh row and disabling stale/broken rows over overwriting credentials in place.148- Expected payload fields: `driverPhoneE164`, `sutPhoneE164`, `driverAuthArchiveBase64`, `sutAuthArchiveBase64`, and optional `groupJid`.149- Keep credential material out of the repo, logs, PRs, and screenshots. Redact phone numbers unless the operator explicitly asks for local debugging.150- Validate with `pnpm openclaw qa whatsapp --credential-source convex --credential-role maintainer --provider-mode mock-openai` and preserve artifact paths plus redacted pass/fail summaries.151- If WhatsApp expires or invalidates a linked Web session, relink locally, package fresh auth archives, add a new Convex row, then disable the stale row.152153## Character evals154155Use `qa character-eval` for style/persona/vibe checks across multiple live models.156157```bash158pnpm openclaw qa character-eval \159 --model openai/gpt-5.4,thinking=xhigh \160 --model openai/gpt-5.2,thinking=xhigh \161 --model openai/gpt-5,thinking=xhigh \162 --model anthropic/claude-opus-4-6,thinking=high \163 --model anthropic/claude-sonnet-4-6,thinking=high \164 --model zai/glm-5.1,thinking=high \165 --model moonshot/kimi-k2.5,thinking=high \166 --model google/gemini-3.1-pro-preview,thinking=high \167 --judge-model openai/gpt-5.4,thinking=xhigh,fast \168 --judge-model anthropic/claude-opus-4-6,thinking=high \169 --concurrency 16 \170 --judge-concurrency 16 \171 --output-dir .artifacts/qa-e2e/character-eval-<tag>172```173174- Runs local QA gateway child processes, not Docker.175- Preferred model spec syntax is `provider/model,thinking=<level>[,fast|,no-fast|,fast=<bool>]` for both `--model` and `--judge-model`.176- Do not add new examples with separate `--model-thinking`; keep that flag as legacy compatibility only.177- Defaults to candidate models `openai/gpt-5.4`, `openai/gpt-5.2`, `openai/gpt-5`, `anthropic/claude-opus-4-6`, `anthropic/claude-sonnet-4-6`, `zai/glm-5.1`, `moonshot/kimi-k2.5`, and `google/gemini-3.1-pro-preview` when no `--model` is passed.178- Candidate thinking defaults to `high`, with `xhigh` for OpenAI models that support it. Prefer inline `--model provider/model,thinking=<level>`; `--thinking <level>` and `--model-thinking <provider/model=level>` remain compatibility shims.179- OpenAI candidate refs default to fast mode so priority processing is used where supported. Use inline `,fast`, `,no-fast`, or `,fast=false` for one model; use `--fast` only to force fast mode for every candidate.180- Judges default to `openai/gpt-5.4,thinking=xhigh,fast` and `anthropic/claude-opus-4-6,thinking=high`.181- Report includes judge ranking, run stats, durations, and full transcripts; do not include raw judge replies. Duration is benchmark context, not a grading signal.182- Candidate and judge concurrency default to 16. Use `--concurrency <n>` and `--judge-concurrency <n>` to override when local gateways or provider limits need a gentler lane.183- Scenario source is YAML-only under `qa/scenarios/`: use `index.yaml` and184 per-scenario `*.yaml` files with top-level `title`, `scenario`, and optional185 `flow`. Never add fenced `qa-scenario` / `qa-flow` Markdown files.186- For isolated character/persona evals, write the persona into `SOUL.md` and blank `IDENTITY.md` in the scenario flow. Use `SOUL.md + IDENTITY.md` only when intentionally testing how the normal OpenClaw identity combines with the character.187- Keep prompts natural and task-shaped. The candidate model should receive character setup through `SOUL.md`, then normal user turns such as chat, workspace help, and small file tasks; do not ask "how would you react?" or tell the model it is in an eval.188- Prefer at least one real task, such as creating or editing a tiny workspace artifact, so the transcript captures character under normal tool use instead of pure roleplay.189190## Codex CLI model lane191192Use model refs shaped like `codex-cli/<codex-model>` whenever QA should exercise Codex as a model backend.193194Examples:195196```bash197pnpm openclaw qa suite \198 --provider-mode live-frontier \199 --model codex-cli/<codex-model> \200 --alt-model codex-cli/<codex-model> \201 --scenario <scenario-id> \202 --output-dir .artifacts/qa-e2e/codex-<tag>203```204205```bash206pnpm openclaw qa manual \207 --model codex-cli/<codex-model> \208 --message "Reply exactly: CODEX_OK"209```210211- Treat the concrete Codex model name as user/config input; do not hardcode it in source, docs examples, or scenarios.212- Live QA preserves `CODEX_HOME` so Codex CLI auth/config works while keeping `HOME` and `OPENCLAW_HOME` sandboxed.213- Mock QA should scrub `CODEX_HOME`.214- If Codex returns fallback/auth text every turn, first check `CODEX_HOME`,215 relevant secret-backed auth, and gateway child logs before changing216 scenario assertions.217- For model comparison, include `codex-cli/<codex-model>` as another candidate in `qa character-eval`; the report should label it as an opaque model name.218219## Repo facts220221- Seed scenarios live in `qa/scenarios/index.yaml` and222 `qa/scenarios/<theme>/*.yaml`.223- Main live runner: `extensions/qa-lab/src/suite.ts`224- QA lab server: `extensions/qa-lab/src/lab-server.ts`225- Child gateway harness: `extensions/qa-lab/src/gateway-child.ts`226- Synthetic channel: `extensions/qa-channel/`227228## What “done” looks like229230- Full suite green for the requested lane.231- User gets:232 - watch URL if applicable233 - pass/fail counts234 - artifact paths235 - concise note on what was fixed236237## Common failure patterns238239- Live timeout too short:240 - widen live waits in `extensions/qa-lab/src/suite.ts`241- Discovery cannot find repo files:242 - point prompts at `repo/...` inside seeded workspace243- Subagent proof too brittle:244 - prefer stable final reply evidence over transient child-session listing245- Harness “rebuild” delay:246 - dirty tree can trigger a pre-run build; expect that before ports appear247248## When adding scenarios249250- Add or update scenario YAML under `qa/scenarios/`; do not add `.md` scenario251 files or fenced YAML blocks.252- Keep kickoff expectations in `qa/scenarios/index.yaml` aligned253- Add executable coverage in `extensions/qa-lab/src/suite.ts`254- Prefer end-to-end assertions over mock-only checks255- Save outputs under `.artifacts/qa-e2e/`