conformance-debug · git:20260829.d2a93b0 · 2026-08-29 · sha256 0495575f8197aa95

conformance-debug git:20260829.d2a93b0A

Immutable. This exact content never changes and is served at /api/v1/blob/0495575f8197aa95.

---
name: conformance-debug
description: Fast, targeted debugging of the Harness Conformance Lab (conformance/) — use whenever a conformance check fails (locally or in CI), a harness TUI check needs poking at directly, or you're about to guess at a fix instead of reproducing the real failure. Covers the --sandbox fast loop, reading evidence/verdict.json, and telling a shared-setup failure apart from a per-harness bug.
---

# Debugging the Harness Conformance Lab

The Lab runs the real vendor CLI (Claude Code, Codex, OpenCode, Antigravity)
against a synthetic provider in a disposable Docker container — one
`conformance/harnesses/<vendor>/scenarios.py` per harness. A full gate run
(`python3 conformance/lab.py --harness <h>`) takes 6-11 minutes and only
tells you pass/fail per check. **Never iterate against that loop.** There is
a ~5-second sandbox loop built for exactly this — reach for it first.

## Step 1 — read the shape of the failure before touching anything

- **The same first check fails identically across all four harnesses**
  (e.g. `tui-reached-prompt` / `tui-started`, with a near-empty first
  `.raw` capture) → the shared **setup script** died before any harness TUI
  ever launched. Do not touch `scenarios.py`'s TUI-driving/wait logic — the
  bug is in the setup steps every vertical shares (`uze market add`,
  `uze plugin install ...`), or in `uze` itself.
- **Only one harness fails** → the bug is specific to that harness's
  `crates/uze-integrations/src/<vendor>/` module or its `scenarios.py`.
- **A late-stage check fails after earlier ones pass** → the TUI/harness
  came up fine; the bug is in that specific interaction (skills, MCP,
  hooks), not in provisioning.

The setup scripts run with `set -e` and `>/dev/null 2>&1` on purpose (a
clean gate run must be quiet) — which means a setup failure is **silent**
in the normal evidence: a 0-byte or garbage-looking first `.raw` file is
the signature of "the container died during setup," not a TUI bug. Don't
debug the wait loop for a dead process.

## Step 2 — reproduce in ~5 seconds, not ~10 minutes

`lab.py` has a sandbox mode built for this (`--help` documents it in full):

```bash
# Runs one command inside the real, fully-provisioned harness container
# (fixture marketplace + synthetic provider already up), then tears down.
python3 conformance/lab.py --harness <claude|codex|opencode|antigravity> \
  --sandbox -- "<shell command>"
```

Use it to poke at the exact step you suspect, e.g.:

```bash
python3 conformance/lab.py --harness opencode --sandbox -- \
  "uze market add /work/market >&2; uze plugin install mcp-plugin@uze-lab; uze doctor"
```

This is the single highest-leverage move: it isolates "did `uze` do the
right thing here" from "did the TUI render the right thing," in seconds,
with real (uncensored) stdout/stderr — instead of guessing from a failed
gate run's opaque assertion list.

- `--shell` instead of `-- cmd` for an interactive rootless shell in the
  container (needs a real TTY — from a non-interactive agent session, use
  `-- cmd` instead).
- `--keep` leaves the provider container + network alive after exit, if
  you need more than one round without re-provisioning.
- To test a **vendor CLI's own behavior** in isolation (does it parse this
  path/argument the way we assume?) — the sandbox drops you next to the
  real vendor binary. Never guess from vendor docs or your own priors about
  CLI argument parsing; run it and read the real exit code/stderr. This is
  exactly what found a case where `agy plugin install <path>` parsed a
  `name@marketplace`-shaped path segment as a marketplace selector instead
  of a literal path — undocumented, only visible by running it.

## Step 3 — after any Rust change, rebuild the Lab image before trusting a run

The image bakes a release build of `uze` plus the real vendor CLIs. A stale
image silently tests your *old* code:

```bash
docker build -f conformance/Dockerfile -t conformance-harness:latest .
```

Rebuilds are fast when only `crates/`/`src/` changed (cached apt/provision
layers); expect ~30-40s from a clean base, ~5-10s incremental.

## Reading evidence

Each run's `outdir` (default `/tmp/harness-conformance/<harness>/run<N>/`,
or `$AGY_OUTDIR`) holds:

- `NN_<checkpoint>.raw` — plain-text screen snapshot at that checkpoint.
  Empty/near-empty on an early one is the setup-died signature (Step 1).
- `<phase>.typescript` + `.timing` — full terminal recording (`script(1)`
  format); replay with `make lab-replay` for the actual pixels-and-timing
  session, not just a text snapshot.
- `verdict.json` — structured result per check, including `gate.adjudication`
  (`asserted` vs `unregistered_adapt` vs escalated) and the run manifest
  (harness/uze/image/fixture versions — check `version_drift` first if nothing
  else changed).
- `--discovery` (flag on `lab.py`) additionally captures the raw
  provider-side (synthetic-LLM) requests — use it when the question is
  "what did the model/tool-call payload actually contain," not "did the
  TUI render it."

## Don't

- Don't iterate against the full gate run (`lab.py --harness <h>` with no
  `--sandbox`) while narrowing down a cause — that's the ~10-minute loop,
  reserved for confirming the fix at the end.
- Don't assume vendor CLI behavior from memory or docs when a one-line
  `--sandbox -- "<vendor-cli> ..."` call gets you the real answer.
- Don't treat "all 4 harnesses broke on the same push" as 4 separate bugs —
  check the shared setup path first (Step 1).