Live testing — the community practice
This page codifies the practice developed while live-testing all 24
catalog workflows end-to-end on a real server with the oxo-flow CLI
(2026-08 campaign, 23 live-verified and counting). It is a reference
paradigm, not a policy: follow the checklist with or without an AI
assistant — the AI layer (the live-test skill)
just executes the same steps faster.
Why live-test at all — dry-run proves the graph parses and the
plan is schedulable; it does not prove tools run, environments resolve,
or outputs validate. Every workflow in the catalog must therefore pass
a full oxo-flow run against minimal real data before it earns the
live-verified rating. The practice below is the cheapest reliable way
to get there.
The four principles
- Minimal data, maximal coverage — at least 2 samples/pairs, tiny enough to finish in minutes, structured enough that every tool in the pipeline has real signal to compute (see fixture design).
- One generator, three products — genome, annotation and reads are emitted by a single deterministic script. Mixed-origin fixtures are the single most common live-test failure class.
- Never change semantics — fixes tune resource labels, naming, isolation and environment pins; they never alter what the pipeline computes. Anything that needs user-scale data becomes a documented gated contract, not a silent change.
- Evidence over guessing — the checkpoint file is the source of truth for failures (diagnosis); every fix commit cites the live error it addresses.
Before you start
- Group by difficulty. Sort workflows: pure-conda before docker, single-sample before cohort, no-reference before reference-gated. Work the list easy → hard; never deadlock on one repository — park it and move on.
- Right-size the machine. 4 vCPU / 4 GB RAM runs every catalog workflow except the user-data-gated steps; that is deliberate — a small box exposes resource bugs a big one hides. Anything smaller than 2 GB starts failing for the wrong reasons.
- Two lanes, strict. Run at most 2 repositories concurrently. Beyond that, memory contention (conda solver + R + JVM peaks) kills unrelated runs and wastes the whole queue. The harness scripts enforce this with file locks.
- Mirror neutrality. A workflow repository must work against both international sources and CN mirrors — pins go in the repo, mirror choice goes in the environment. Test-environment proxy or mirror configuration must never be committed to a workflow repository.
Fixture design
The pattern that works (one test/fixtures/generate_fixtures.py per
workflow, fixed seed, committed alongside the generated files):
- Reads are derived from the shipped genome — drawn from feature regions so alignment, quantification and annotation steps all see signal; ~0.5 % substitution error; Phred-30+ qualities.
- The annotation is self-consistent — unique transcript ids, exons
inside transcript bounds,
gene_idmatching the gene feature. Tools like gffutils/RSeQC hard-fail on reused ids across loci (End of last exon (2000) does not match end of feature (500)). - Tool-shaped signal, added on demand — preseq needs a duplicate
count curve (≥4 multiplicity levels); DamageProfiler needs per-position
depth above its
-tthreshold; DADA2 needs amplicon template structure; circRNA callers need back-splice junctions; DESeq2 needs a two-condition signal. Add each only when a tool demands it, and note why in the generator docstring. - Read-name contracts —
/1/2mate suffixes (AdapterRemoval v2 rejects files without them); duplicate tags before the suffix (@read_dup1/1, never@read/1_dup1— bwa mem pairs by name after stripping the trailing/1or/2). - Paired-end arithmetic — inserts sized so mergers (AdapterRemoval) can overlap; spliced reads assemble exon segments along the transcription direction.
- Regeneration is idempotent — the generator overwrites its
outputs; one-shot post-processors (e.g. adding duplicates) must start
from the pristine fixtures (
git checkoutthe base first), or they stack tags on tags.
Resource adaptation
The engine gives every rule two pairs of placeholders — {threads} /
{memory} (the declaration, pool semantics) and {effective_threads}
/ {effective_memory_mb} (the machine-clamped reality). Rules that
embed resource-sized flags use the effective pair:
bwa -t {effective_threads},samtools sort --threads {effective_threads} -m 512M(samtools' default 768 MB/thread over-allocates and dies withcouldn't allocate memory for bam_memon small boxes);-Xmx{effective_memory_mb}mfor JVM tools instead of HPC-scale hardcodes;- container
--memoryis clamped to the machine by the engine itself (a72Glabel runs as--memory 4Gon a 4 GB box — cgroup-aware tools like Cell Ranger see an honest limit); - the machine ceiling counts swap — RAM + swap is the backable
budget the kernel will actually use under pressure; pass
--max-memoryto pin it to RAM only when latency matters more than headroom.
These are the semantics-preserving fixes: the workflow still declares its upstream labels, only the tool-facing numbers adapt.
Gated contracts
Some pipelines legitimately require data no repository can ship. Instead of failing or faking, the port documents a gated contract:
- the gate is an explicit config switch (e.g.
run_gtdbtk = false,annotate_vep = false,skip_ref_downloads = true); - the README states exactly which rules the gate covers and what the user must provide to lift it (GTDB-Tk's ~100 GB database, a VEP cache, a GRCh38 bundle…);
- the live test runs with the gate closed, and the catalog entry's
quickstart_notesays so honestly.
Gated steps are verified separately (command construction, environment resolution) and rated below live-verified — see the curation ladder.
Execution & monitoring
The scripts/live-test/ harness (documented in the skill
reference) runs
the queue:
- 2 lanes via
flockon two lock files — blocked runners form a FIFO; only two repositories execute at any moment. - Resume-aware requeues — a runner keeps
.oxo-flowwhen the workflow files are unchanged (the engine's input manifests then invalidate exactly the affected rules); a changed workflow wipes and restarts. - A supervisor parks repositories that stall (>30 min without rule activity) or fail three times with the same signature — parked repos stay out until a fix lands.
- Verdicts are the
run end: exit=Nlines; exit 0 is the only pass. On success the engine also writes.oxo-flow/reports/report-*.json— the reference snapshot for reviewers.
Run the supervisor and a disk watchdog from cron on the test server — after reboots, crontabs vanish and the queue degrades silently.
Diagnosis
When a run fails, read the checkpoint, not the log tail:
python3 - <<'EOF'
import json
d = json.load(open(".oxo-flow/checkpoint.json"))
print("failed:", d["failed_rules"])
for k, v in d["rule_runs"].items():
if v.get("exit_code") not in (0, "0") and v.get("exit_code") is not None:
print(k, "|", v.get("exit_code"), "|", str(v.get("stderr_tail", ""))[-400:])
EOF
Two traps the campaign hit repeatedly:
- The visible ✗ is not the failure. The engine aborts fast and the
last scheduled rule's WARN lines sit right before the generic
workflow execution failed— the real failing rule is infailed_rules, its stderr inrule_runs. Shell exited 0 but declared outputs are missingmeans the tool wrote somewhere else (cwd quirks,%-placeholder naming, stale state) — locate the actual artifacts before touching the shell.
Recurring failure classes, each with its live symptom, root cause and fix pattern, live in the failure catalog — consult it before debugging from scratch.
Fix discipline
- One commit per fix, body citing the live evidence
(
Live: 'sort: invalid option -- F' from the pinned image's samtools…). - Push, sync the test server, requeue — and verify the fixed tree actually ran (server trees drift: stale checkouts, partial syncs, mirrors lagging on branch refs).
- Update the docs the fix affects in the same change.
- A second reviewer diffs and merges; live-verified evidence is stamped only against the exact tree that passed.
- Record every new failure class in the failure catalog — that file is the accumulating institutional memory this page promises. Update it as part of the fix that discovered the class.
With or without AI
- Without AI: follow the checklists above literally; the harness scripts run the queue for you; the failure catalog short-circuits most debugging.
- With AI (Claude Code): install the
live-testskill (/live-test), point it at a server and a batch of repositories; it executes the same loop — group, fixture-check, run, diagnose via checkpoint, fix, archive the finding — with the campaign scripts and catalog as its reference.
Either way, the acceptance bar is the same: every catalog workflow runs end-to-end via the CLI on real minimal data, or it is honestly documented as gated.