contents
GitHub Action Any CI k6 MCP / AI tools Scheduled checks Download the catalog fd runner (design)GitHub Action
Drop a battery into CI. This runs a subset of specimens against your client and fails the build if the grade drops below a threshold — the "your error handling regressed" check nobody has:
# .github/workflows/faultydata.yml
name: faultydata battery
on: [push]
jobs:
grade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: mint battery
run: |
TOKEN=$(curl -s -X POST https://faultydata.com/battery \
-d "{\"client\":\"$GITHUB_REPOSITORY@$GITHUB_SHA\"}" \
| jq -r .token)
echo "FD_TOKEN=$TOKEN" >> $GITHUB_ENV
- name: run hang specimen — must time out, not hang forever
run: |
timeout 8 curl -s -o /dev/null \
"https://faultydata.com/run/$FD_TOKEN/FD-2026-017" \
&& echo "FAIL: returned" || true
- name: report outcome
run: |
curl -s -X POST \
"https://faultydata.com/run/$FD_TOKEN/FD-2026-017/report" \
-d '{"outcome":"timeout","ms":8000}'
- name: report card
run: echo "card: https://faultydata.com/report/$FD_TOKEN"
A proper faultydata/grade@v1 action wrapping the
whole battery is on the roadmap — the YAML above works today with nothing
but curl.
Any CI, anywhere
The whole integration surface is HTTP — no SDK, no agent, no vendor plugin. If your CI can run curl, it can run a battery:
# Makefile target
grade:
TOKEN=$$(curl -s -X POST https://faultydata.com/battery \
-d '{"client":"$(APP)-ci"}' | jq -r .token) && \
for id in FD-2026-001 FD-2026-011 FD-2026-017; do \
timeout 6 curl -s -o /dev/null https://faultydata.com/run/$$TOKEN/$$id ; \
done && \
echo "report: https://faultydata.com/report/$$TOKEN"
With an API key (600 rpm) a full
76-specimen battery fits comfortably in a CI window. Set
X-API-Key as a CI secret and forget it exists.
k6
Specimens double as load-test targets that behave like a dependency having a bad day — weighted composites especially:
import http from "k6/http";
import { check } from "k6";
export default function () {
const r = http.get("https://faultydata.com/r/FD-2026-003");
check(r, {
"client handled degraded mode": (x) =>
x.status === 200 || x.status === 500 || x.status === 503,
});
}
MCP — for the machines that write the code
faultyd -mcp exposes the registry as MCP tools:
list_specimens, get_specimen,
replay_instructions, start_battery,
get_report. Point Claude Desktop, Cursor, or any MCP client at
the binary and your agent can pick specimens, generate test harnesses, and
read report cards. Config snippet:
{
"mcpServers": {
"faultydata": {
"command": "/usr/local/bin/faultyd",
"args": ["-mcp"]
}
}
}
Scheduled checks
A cron job that verifies the registry itself is alive — and, more usefully, that your client still handles the classic faults after every dependency bump:
# weekly: does our fetch wrapper still redirect-loop?
0 6 * * 1 timeout 10 curl -s -o /dev/null -w "%{http_code} %{num_redirects}\n" \
-L https://faultydata.com/r/FD-2026-020
Download the catalog
GET /api/specimens returns the entire registry —
every specimen, every signature, every anatomy. Mirror it, embed it in your
own test fixtures, run the signatures against a local mock. The Pwned
Passwords lesson: giving the corpus away is what makes the registry
canonical. The catalog is free; the authority to say "FD-2026-028" and have
it mean something is not.
fd runner — design
On the roadmap: a small CLI that runs the full battery honestly — executes each specimen against your HTTP client command, classifies the real outcome itself (no self-reporting), submits results, prints the report URL. If you want to help design it, the specimen schema is public and the report API is documented at /api.