getsentry/vitest-evals is the GitHub Actions reporting
surface for vitest-evals. It reads Vitest JSON output and publishes GitHub
job summaries, workflow annotations, and optional Check Runs.
Public docs mirror this guide at vitest-evals.sentry.dev/docs/github.
Use JSON as the eval artifact because it preserves task.meta.eval and
task.meta.harness; JUnit XML does not carry the full eval metadata.
jobs:
evals:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install
- name: Run evals
run: |
pnpm exec vitest run evals \
--reporter=vitest-evals/reporter \
--reporter=json \
--outputFile.json=vitest-results.json
- uses: getsentry/vitest-evals@v0
if: always()
with:
results: vitest-results.jsonThe report step still runs after failed evals because of if: always(). The
job remains failed when the eval step fails.
Add checks: write and enable publish-check when you want a dedicated PR
check with a custom secondary status line (the Check Run title).
permissions:
contents: read
checks: write
steps:
- uses: getsentry/vitest-evals@v0
if: always()
with:
results: vitest-results.json
publish-check: true
min-pass-rate: 0.8On pull_request, GitHub sets GITHUB_SHA to a temporary merge commit. PR
checks and required status attach to the head commit. The action therefore
resolves the Check Run SHA in this order:
- explicit
shainput /--sha GITHUB_PR_HEAD_SHA(optional override env)pull_request.head.shafromGITHUB_EVENT_PATHGITHUB_SHA
You usually do not need to pass sha yourself. If you must override:
- uses: getsentry/vitest-evals@v0
with:
results: vitest-results.json
publish-check: true
sha: ${{ github.event.pull_request.head.sha || github.sha }}A Check Run is a separate GitHub check from the workflow job row. Use it
when you want the PR checks list secondary line to show the gate title
(for example Eval pass rate 90.2% — floor 80.0%) instead of canned job text.
Recommended defaults when publish-check: true and a gate is set:
- the Check Run conclusion follows the gate
- if the Check Run publishes successfully, the action step stays green
(
soft-faildefault) so the Check Run owns PR status - if Check Run publishing is skipped or fails, the step still fails on a rejected gate so you do not silently lose the status signal
- set
soft-fail: falsewhen you also want the workflow job red
If the token or permission is missing, the action keeps the job summary and workflow annotations and warns instead of failing solely for the missing check.
By default the action reports scores and failures without failing the step. Use a gate when CI should own green/red:
fail-on-failures: truerequires every eval case to passmin-pass-rate: 0.8requires at least 80% of eval cases to passmin-score-average: 0.75requires the average score to stay at or above 0.75
When a gate is configured:
statusand Check Run conclusion/title follow the gate- the Check Run title /
gate-titleshow the decision (for exampleEval pass rate 63.7% — required 80.0%) - case misses become warnings when the gate still passes, so a green check is not flooded with red failure annotations
- non-eval / infrastructure failures still fail hard
- missing scores fail closed when
min-score-averageis set - with
publish-check: true, a successfully published Check Run soft-fails the step so the Check Run owns PR status (override withsoft-fail: false)
Without a gate, status still mirrors the raw report (any case failed), but
the step stays green unless you set fail-on-failures / a floor. Use the
evals-* count outputs for raw tallies either way.
- id: report
uses: getsentry/vitest-evals@v0
with:
results: eval-results/*.json
publish-check: true
min-pass-rate: 0.8
# ${{ steps.report.outputs.status }} # passed | failed (gate decision)
# ${{ steps.report.outputs.gate-title }} # Eval pass rate 90.0% — floor 80.0%
# ${{ steps.report.outputs.pass-rate }} # 0.90
# ${{ steps.report.outputs.evals-failed }} # raw miss count
# ${{ steps.report.outputs.check-url }} # published Check Run URLfail-on-failures: true is equivalent to min-pass-rate: 1.
If you only need the rich markdown report on the workflow job, leave
publish-check off. That keeps everything under the normal workflow check and
avoids a second Checks API entry on the PR.
- uses: getsentry/vitest-evals@v0
if: always()
with:
results: vitest-results.json
publish-summary: true
min-pass-rate: 0.8Split evals with a matrix, upload one JSON artifact per shard, then reduce them in one final reporting job. Keep shard jobs from failing the workflow on qualitative misses when the reducer owns the gate:
jobs:
evals:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install
- name: Run eval shard
# continue-on-error lets qualitative misses still upload JSON.
# Missing result files should still fail the shard.
id: run
continue-on-error: true
run: |
pnpm exec vitest run evals \
--shard=${{ matrix.shard }}/4 \
--reporter=vitest-evals/reporter \
--reporter=json \
--outputFile.json=vitest-results-${{ matrix.shard }}.json
- name: Require eval results
if: steps.run.conclusion != 'skipped'
run: test -f vitest-results-${{ matrix.shard }}.json
- uses: actions/upload-artifact@v4
if: success()
with:
name: vitest-evals-${{ matrix.shard }}
path: vitest-results-${{ matrix.shard }}.json
report:
if: always()
needs: [evals]
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: vitest-evals-*
path: eval-results
merge-multiple: true
- uses: getsentry/vitest-evals@v0
with:
results: eval-results/*.json
publish-check: true
check-name: eval score
min-pass-rate: 0.8Optional: publish a per-shard job summary (no Check Run) so each matrix job shows its own metric table and quality misses, while the reducer still owns the aggregate gate:
- uses: getsentry/vitest-evals@v0
if: always()
with:
results: vitest-results-${{ matrix.shard }}.json
publish-summary: true
publish-check: false
fail-on-failures: falseUse fail-on-failures: true instead of a pass-rate floor when every case must
pass. If shard jobs are already required and fail on their own, leave gates off
on the reducer.
| Input | Default | Description |
|---|---|---|
results |
vitest-results.json |
Vitest JSON result files. Supports paths, * and ** globs, and newline-separated entries. |
publish-summary |
true |
Write a GitHub Actions job summary. |
publish-annotations |
true |
Emit GitHub workflow annotations for failed evals. |
publish-check |
false |
Publish one GitHub Check Run for the combined report. Attaches to PR head on pull_request. |
check-name |
vitest-evals |
Name of the GitHub Check Run. |
github-token |
${{ github.token }} |
Token used for Check Run publishing. |
sha |
PR head, else GITHUB_SHA |
Commit SHA for the Check Run. |
fail-on-failures |
false |
Fail the action when any eval case failed. Equivalent to min-pass-rate: 1. |
soft-fail |
auto | Keep the step green when a published Check Run owns a failed gate. Defaults to true only after a successful Check Run publish. |
min-pass-rate |
unset | Minimum fraction of eval cases that must pass (0-1). |
min-score-average |
unset | Minimum average eval score across scored cases (0-1). |
max-annotations |
unset | Maximum number of failure annotations to publish. Check Run annotations are capped at 50 by GitHub. |
max-failures |
unset | Maximum number of detailed failures to include in summaries and checks. |
| Output | Description |
|---|---|
status |
Effective CI status: passed or failed. Follows the gate when one is configured; otherwise mirrors the raw report. |
results-count |
Number of Vitest JSON result files included. |
evals-total |
Total eval cases included in the report. |
evals-passed |
Passed eval cases included in the report. |
evals-failed |
Failed eval cases included in the report. |
pass-rate |
Eval pass rate as a 0-1 ratio (0.80), or n/a. |
score-average |
Average eval score across the combined report. |
score-minimum |
Minimum eval score across the combined report. |
gate-status |
Same value as status (kept for explicit gate wiring). |
gate-title |
Short gate title used for Check Runs and suite-level annotations. |
gate-message |
Human-readable gate decision. |
check-url |
URL of the published GitHub Check Run, when available. |
Emit JUnit only when another CI tool needs XML:
pnpm exec vitest run evals \
--reporter=vitest-evals/reporter \
--reporter=json \
--reporter=junit \
--outputFile.json=vitest-results.json \
--outputFile.junit=tests.junit.xmlVitest auto-adds its built-in github-actions reporter when
GITHUB_ACTIONS=true. That reporter can write a generic job summary such as
Test Files: 1 pass · 1 total, which is usually noise next to the
vitest-evals summary. Prefer reading the vitest-evals job summary and
Check Run from the report step; silencing Vitest's auto reporter is version-
specific and left to each consumer's Vitest config.