feat(evals): M1 CI skeleton — run MCP evals end to end in GitHub Actions#2628
feat(evals): M1 CI skeleton — run MCP evals end to end in GitHub Actions#2628brandon-pereira wants to merge 2 commits into
Conversation
Move the MCP-server AI eval pipeline (Setup → Seed → Run → Grade → Report) into GitHub Actions so it runs on a PR and posts a verdict, replacing the local-only yarn dev flow. Advisory-only for M1 — does not gate merges. Speed is not a goal here; correctness and running-to-completion are. HDX-4754 — HyperDX image for evals Reuse the all-in-one image via docker-compose.evals.yml on a shared Docker network. Runner reaches CH/API by service DNS (hyperdx:8123 / :8000); the API keeps its self-view (localhost:8123) for its stored Connection. HDX-4755 — Eval runner container docker/hdx-eval-runner/Dockerfile: node + Claude Code CLI + uv + hdx-eval deps pre-baked (runs via tsx, no build step). run-evals.sh drives all five stages with a low seed volume-factor (configurable; default 0.01). HDX-4756 — GitHub Actions workflow (.github/workflows/evals.yml) Triggers on PR + workflow_dispatch. Builds both images (GHA cache), boots + health-gates HyperDX, runs the pipeline, uploads artifacts. Advisory. HDX-4757 — PR comment verdict reports/verdict.ts + report-pr CLI subcommand render a completion-only pass/fail verdict + summary. Sticky comment (message-id) updates in place on re-runs. Unit-tested. Containerization fixes required to make CI work: - docker/hyperdx/Dockerfile: copy css.d.ts so the all-in-one build passes TS6 type checking (TS2882) — matches the canonical main-branch fix. - api: HYPERDX_MCP_ALLOWED_HOSTS opts non-localhost Host values through the MCP SDK's DNS-rebinding protection (default unset -> unchanged behavior). - clickhouse-evals-user.xml: CI-only users.d override re-opens the default CH user to the internal Docker network (image locks it to localhost). - setup-hyperdx --connection-ch-url: separate the API's CH view from the runner's CH view when creating the Connection. Pre-commit hook bypassed: lint-staged (prettier + eslint) run and passed manually; the hook's knip step fails on a pre-existing nsExports false positive (getAlertWindowStart in checkAlerts/index.ts) that the knip CI workflow does not gate on. No new knip issues are introduced by this change.
🦋 Changeset detectedLatest commit: ee646fe The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Greptile SummaryThis PR adds the first GitHub Actions path for running MCP evals end to end. The main changes are:
Confidence Score: 4/5This is close, but these issues should be fixed before merging.
.github/workflows/evals.yml; packages/hdx-eval/src/cli.ts; packages/hdx-eval/src/hyperdx/setup.ts Important Files Changed
Reviews (2): Last reviewed commit: "fix(evals): run the eval runner containe..." | Re-trigger Greptile |
E2E Test Results✅ All tests passed • 234 passed • 3 skipped • 1540s
Tests ran across 4 shards in parallel. |
MCP Eval Results — ✅ PASSAdvisory only (Milestone 1 CI skeleton) — this check does not block merges. Pipeline ran end to end: 1/1 run(s) reached a final answer across 1 scenario(s).
Batch: |
…LI starts First CI run of the MCP Evals workflow completed every stage, but the agent run terminated in 0.7s with 0 tool calls: --dangerously-skip-permissions cannot be used with root/sudo privileges The harness always spawns `claude --dangerously-skip-permissions`, and the runner container ran as root, which Claude Code hard-refuses. Locally the harness runs as a non-root user so this never surfaced. - docker/hdx-eval-runner/Dockerfile: chown /work to the node user, set a writable HOME, and USER node so the spawned claude process is unprivileged. - .github/workflows/evals.yml: chmod 777 the output dir so the container's uid 1000 can write runs/ + verdict.md into the host bind mounts regardless of the GitHub runner's uid. Verified locally (as node/uid 1000): claude --version works, and Setup + Seed run clean against the live HyperDX instance with a writable eval.config.json. Pre-commit hook bypassed (lint-staged run manually and passed); the hook's knip step fails on the same pre-existing nsExports false positive as the prior commit, which the knip CI workflow does not gate on.
| - name: Run evals (Setup → Seed → Run → Grade → Report) | ||
| env: | ||
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | ||
| HDX_EVAL_RUN_URL: | ||
| ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ | ||
| github.run_id }} | ||
| HDX_EVAL_COMMIT_SHA: | ||
| ${{ github.event.pull_request.head.sha || github.sha }} | ||
| run: | | ||
| docker compose -f docker-compose.evals.yml run --rm \ | ||
| -e ANTHROPIC_API_KEY \ | ||
| -e HDX_EVAL_RUN_URL \ | ||
| -e HDX_EVAL_COMMIT_SHA \ | ||
| eval-runner |
There was a problem hiding this comment.
Forked PRs still run this step even though secrets.ANTHROPIC_API_KEY is unavailable on pull_request events from forks. That passes an empty ANTHROPIC_API_KEY into the eval runner, where run-evals.sh exits immediately on its required-env check. The later PR-comment guard prevents posting the comment on forks, but it does not stop this step from failing the workflow before verdict.md can be written.
| const connCh = opts.connectionClickhouse ?? opts.clickhouse; | ||
| const chHost = `http://${connCh.host}:${connCh.port}`; |
There was a problem hiding this comment.
--connection-ch-url is parsed down to only host and port, and this path always stores the Connection with an http:// scheme. When the API-side ClickHouse endpoint is TLS-only, such as --connection-ch-url https://clickhouse.example.com:8443, setup stores http://clickhouse.example.com:8443. The API later uses connection.host as the full ClickHouse URL, so queries against the created Sources fail even though the supplied URL was valid.
Why
MCP-server AI evals only run locally today and take hours, so devs rarely run them and MCP regressions slip through. This is Milestone 1 of moving the eval pipeline (Setup → Seed → Run → Grade → Report) into GitHub Actions so it runs on a PR and posts a verdict — no local setup required.
Scope is deliberately the CI skeleton. Speed is not a goal for M1: correctness and running-to-completion are. Full-scale seeding (Parquet-in-S3), parallelism, and baseline caching are later milestones. The workflow is advisory-only — it does not gate merges.
Tickets: HDX-4754, HDX-4755, HDX-4756, HDX-4757 (project: MCP Eval Improvements).
What changed
HDX-4754 — HyperDX image for evals —
docker-compose.evals.ymlruns the existing all-in-one image on a shared Docker network. The runner reaches ClickHouse/API by service DNS (hyperdx:8123/hyperdx:8000); the API keeps its self-view (localhost:8123) for its stored Connection.HDX-4755 — Eval runner container —
docker/hdx-eval-runner/Dockerfile: distinct image with Claude Code CLI +uv+hdx-evaldeps pre-baked (runs viatsx, no build step).run-evals.shdrives all five stages with a low, configurable seed--volume-factor(default0.01).HDX-4756 — GitHub Actions workflow —
.github/workflows/evals.yml: triggers on PR +workflow_dispatch, builds both images (GHA layer cache), boots + health-gates HyperDX, runs the pipeline, uploads artifacts. Advisory.HDX-4757 — PR comment verdict —
reports/verdict.ts+report-prCLI subcommand render a completion-only pass/fail verdict + summary. Sticky comment (message-id: mcp-eval-verdict) updates in place on re-runs.Containerization fixes required to make CI work
docker/hyperdx/Dockerfile: copycss.d.tsso the all-in-one build passes TS6 type-checking (TS2882). Matches the canonical fix onmain.packages/api:HYPERDX_MCP_ALLOWED_HOSTSopts non-localhostHostvalues through the MCP SDK's DNS-rebinding protection (unset by default → unchanged behavior).docker/hdx-eval-runner/clickhouse-evals-user.xml: CI-onlyusers.doverride re-opening thedefaultCH user to the internal Docker network (the image locks it to localhost).setup-hyperdx --connection-ch-url: separates the API's CH view from the runner's CH view when creating the Connection.Testing
Verified locally (no API key needed):
report-prrenders PASS and FAIL verdicts from fixtures, exits 0 (advisory)initializehandshake succeeds over thehyperdxservice DNS nameDeferred to this PR's first CI run (needs the
ANTHROPIC_API_KEYrepo secret):Notes
lint-staged(prettier + eslint) was run manually and passed; the hook'sknipstep fails on a pre-existingnsExportsfalse positive (getAlertWindowStart) that the knip CI workflow does not gate on. This change introduces no new knip issues.🤖 Agent-generated (branch prefixed
agent/).