Files
gstack/docs/evals/census-red.md
T
Garry Tanandcapy-ai[bot] daf661374f v1.93.10.0 wave(docs): #3590 #3591 #3819 #3618 #3824 #3828 (#3847)
* Attribute every paid trial's cost: bind label-named records by testName, price aborted SDK sessions from streamed usage, model-free cases cost $0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: case cost adds streamed sessions to record sums and keeps record cost_known

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: the paid report headline counts ledger-only session cost once

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a cost-only record never sets the trial model

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: recordE2E writes cost_case_id only for a registered label-named case

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CHANGELOG for paid-report cost attribution (Unreleased)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: the headline adds only streamed session cost; cache writes without a 5m key

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: how the paid report prices a trial and what stays cost-unknown

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Price PTY sessions from their transcripts: pin a session id, sum the session's and its subagents' usage into the ledger's streamed_cost_usd

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: transcript pricing never throws from a PTY ledger writer

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: pin transcript-cost.ts as non-behavioral at HARNESS_VERSION 6

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: recognise --session-id= and --resume= forms

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CHANGELOG for PTY transcript pricing (Unreleased)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: PTY sessions priced from their transcripts

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: price PTY transcripts without adding a launch argument; native AUQ capture opts out

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: harness notes and CHANGELOG match the transcript claim

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(flaky): gstack-flaky run reruns a failed JUnit test once and ledgers the flake (GSTA-158)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(flaky): stream output, keep unreported runner failures red, fail-closed duplicates and nonzero reruns

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

gstack-flaky never greens an incomplete run: a targeted rerun needs the
runner's summary proving the first run complete, otherwise (and always for
generic runners) the rerun repeats the whole command. A rerun clears a test
only when it exits 0 and the test passes once on its first attempt. Killed
runners exit 3 (FLAKY_COMMAND_KILLED) and signals are forwarded. The first
report is restored at --junit with reruns kept beside it; ids are redacted at
every output; the ledger gets its own GSTACK_FLAKE_LEDGER_JUNIT; fixtures are
registered in FREE_FIXTURES.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: latch cancellation and check whole-command rerun reports

A cancel forwarded to a runner that traps the signal now counts as killed for
every later attempt, so a cancelled job is never rerun or greened. A
whole-command rerun clears nothing when its report has a failed test, misses a
first-run test, or its runner counted failures outside the report.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: whole-command rerun when coverage or runner-flaky tests can fail the run

A coverage threshold or a fail-on-flaky policy fails a run for a reason a
narrowed rerun cannot reproduce, so either forces a whole-command rerun.
--junit refuses an existing file that is not a JUnit report instead of
deleting it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(flaky): rerun the whole command by default; targeted rerun is opt-in

A rerun counts only on the runner's own full green verdict: exit 0, no failed
or missing test, nothing counted outside the report. That covers every way a
run can fail, which a narrowed rerun cannot (a coverage threshold with an lcov
reporter, a fail-on-flaky policy, a test never reached). --rerun-scope test
reruns the failed test alone, with its preconditions documented and the
complete-run checks kept as a safety net. An unreadable existing --junit file
is a usage error, not a crash.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: gstack-flaky CHANGELOG entry and standalone-binaries row (GSTA-158)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(flaky): quarantine registry for repeat flakes (GSTA-158 P2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(flaky): bound renewal dates; match non-ASCII paths in check --base

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(flaky): reword the FLAKY_QUARANTINE_BASE hint so the egress scanner does not read it as a network op

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(agent-sdk): every SDK caller passes the claude_code preset or names why not

The SDK's default system prompt is a bare stub that drops the line telling the
model to run /skill through the Skill tool. runAgentSdkTest now refuses a
missing, empty or bare-string prompt; non-preset callers name a reason; a free
tripwire scans test/ and scripts/ for the same mistake.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: record the SDK prompt guard as a non-behavioral harness change and baseline its tripwire

harness-version: runAgentSdkTest validates systemPrompt before starting; every
live caller already passed the claude_code preset, so no verdict can change.
test-of-test ratchet: the tripwire's subject is the eval harness by design.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: name the preset literal callers pass and the unnamed-prompt refusal

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(eval-plan): frontier models under test (R54)

Haiku and Sonnet are not models under test. dry-run refuses a plan whose every
arm is Haiku or Sonnet unless --tier-question names what it asks about that
tier, warns on a mix, and re-costs the plan on Opus 5.5 and GPT-6.1 Sol.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(eval-plan): trim R54 skill prose and raise the eval-plan context ceiling

The R54 paragraph and CHECK: frontier bullet were cut to the minimum; the
exemptions and verdict rule stay in docs/TESTING_INTERNALS.md. The remaining
growth is the feature, so only the eval-plan eager ceiling moves (4887 -> 5458,
the capture helper's value); other skills' ceilings are left as they were.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(eval-plan): pilot start keeps the R54 refusal; --tier-question rejects a flag

Review pass 1 (full depth) found the frontier refusal could be skipped by
starting a pilot on Haiku or Sonnet arms behind a dry run made on other arms,
or behind a dry-run.json written before the check existed. startPilot now
refuses a pilot whose every arm is Haiku or Sonnet unless its dry run recorded
a tier question. dry-run --tier-question no longer takes the next flag as its
answer. The testing doc now says the frontier re-cost is printed for
comparison with the brief cap, not enforced.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(support-tiers): Windows CI in the present tense now that windows-smoke is required (#3806)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: empty commit for a clean check set at the same tree (cancelled orphan free-suite check at 97487df)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(skills): /eval-plan entry names the R54 frontier check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-02 17:44:00 +00:00

7.3 KiB
Raw Blame History

A census went red

A periodic census (evals-periodic.yml: the weekly periodic lane plus the gate census) or a marathon run reports each red verdict as one line. This page takes one red line to a diagnosis, using free commands first. A paid run is allowed only after a repair.

Where the report is:

  • Main (the scheduled weekly run, or a dispatch on main): a comment on the open "Weekly periodic evals: red lane needs triage" issue (marathon: "Weekly marathon evals: red lane needs triage").
  • Any branch: the run's step summary and its census-report-a<attempt> artifact (marathon: marathon-report-a<attempt>). A branch census never comments on or closes main's issue.

Prerequisites

  • A checkout of the census branch with bun install done. Bun 1.4.2 is the tested version.
  • gh authenticated with read access to the repository's Actions (or GH_TOKEN set). eval:pass-rates downloads artifacts through the REST zip route, which also works where gh run download is blocked.
  • The run id: the number at the end of the run URL in the report's first line.

1. Read the red line

A red line in the census report looks like this (one line; a behavior panel with one failed trial shown):

✗ plan-review-prosons-neutral-neg  behavior  FAIL 1/3 (✗✗✓)  t1: assertion (session completed; check failed) — expect(received).not.toMatch(expected)  · cause assertion: session completed; check failed  Expected: not /taste call/i · Received: "a taste call"; t2: assertion  [t1@slice 7, t2@slice 9, t3@slice 9, attempt 1]  evidence: paid-slice-7-a1/shards/skill-e2e-plan-prosons--plan-review-prosons-neutral-neg.t1 (fetch: bun run eval:pass-rates --run 37193478719 --case plan-review-prosons-neutral-neg)  after a repair: bun run scripts/test-paid-shards.ts --tier periodic --case plan-review-prosons-neutral-neg --trials 3
Field What it says
case, kind, verdict the registry id, its kind (rule, behavior, judge) and the verdict with each trial's outcome; the brackets name the slice and attempt of each trial
failure_class the class verdicts read: assertion, contract, timeout or infra
cause what the machine observed (failure_cause, glossary in TESTING_INTERNALS), with its evidence line
Expected / Received the failed matcher's values; for a judge, each failing dimension's gating median (the mean before EVAL_POLICY v3), threshold, sample count and lowest rationale
evidence: the slice artifact and shard directory holding the transcript
after a repair: the command that runs only this case, once a repair exists

The cause is a label. Under EVAL_POLICY v1 it never changes a verdict: a provider_stall or refusal is still a failed trial. Whether an assertion failure was a detector, harness or product fault is your diagnosis, not a machine cause.

2. Inspect the evidence (free)

bun run eval:pass-rates --run <run-id>                 # every red of that census
bun run eval:pass-rates --run <run-id> --case <id>     # one case

Expected output: one block per red, with its history and a local evidence path.

run 37198445662 (main @ 1a2b3c4d5, 2026-10-04T11:29:00Z): 2 verdict red(s)
✗ qa-only/SKILL.md workflow  periodic/full  FAIL  (history: 7/8 verdicts green)
    t1: assertion / cause assertion: session completed; check failed  clarity 3.67 < 4 (3 samples) "…"
    evidence: ~/.gstack/eval-pass-rates-cache/garrytan-gstack/37198445662/paid-slice-5-a1/shards/skill-llm-eval

Only the slices the reds name are downloaded, into <state root>/eval-pass-rates-cache/<owner>-<repo>/<run-id>/. The shard directory holds the eval record JSON (transcript, judge scores), junit.xml and, for runs after the session ledger landed, session-ledger.jsonl (each session's armed budget, elapsed time, end reason and liveness summary). A slice artifact over 64 MB is reported as too large to fetch; download it from the run page or with gh api repos/<owner>/<repo>/actions/artifacts/<id>/zip > slice.zip.

3. Put it in context (free)

bun run eval:pass-rates --reds --runs 10 --branch <branch>      # reds per census by class and cause, all-green probability
bun run eval:pass-rates --headroom --runs 10 --branch <branch>  # slowest session per case vs its armed budget
bun run eval:pass-rates --case <id> --runs 10                   # the case's pass rate per input series

--reds ends with the all-green probability: Π(1 − p_i) over the latest census's cases, with per-case red rates shrunk toward the pooled rate. It is an approximation (verdicts are not proven independent). A case that is red in several censuses is a repeat offender; its fix is structural (a replay corpus, a structured signal or a speedup), not a one-off spelling patch.

What incomplete data means:

Shown Meaning
headroom unknown (no session ledger) the trials predate the session ledger, or the runner wrote none; the case wall beside it is an upper bound, not a session clock
headroom insufficient fewer than 3 session samples; never read as a pass
headroom censored a sample timed out, so its real duration is at least the budget
cause unrecorded the trial was written before failure_cause existed
cause unknown the trial failed with no evidence for any other cause
cost unknown a session's cost was not captured (a PTY session without a priced transcript, an unbilled Codex session, a claude -p session without a result, an aborted Agent SDK session with no priced streamed usage), or no record or session can be attributed to the trial (a multi-case shard's display-label record without cost_case_id); only the known sum is shown
history unavailable (...) the GitHub history could not be read; --gate fails closed

4. Replay and repair (free)

Diagnose before changing code: product defect, test or detector defect, harness defect, or infrastructure. Then reproduce with the smallest free check:

bun test test/detector-corpus-<case>.test.ts      # cases with a replay corpus
bun run eval:pass-rates --reds --dir <evidence dir>   # re-read downloaded artifacts offline

The repair lands with a free regression test that fails before it and passes after it (AGENTS.md, "Validation discipline").

5. After a repair: one paid run

Run the line's after a repair: command, which selects only that case:

bun run scripts/test-paid-shards.ts --tier periodic --case <id>              # a rule case: one diagnostic trial
bun run scripts/test-paid-shards.ts --tier periodic --case <id> --trials 3   # a behavior case: its 3-trial panel

A judge prints an EVALS_JUDGE_SELECTION_JSON=... bun test <file> -t '<pattern>' command instead; a changed judge gets one 3-sample panel.

Rules (see TESTING_INTERNALS):

  • A paid run follows a concrete repair. A red census is never rerun on unchanged inputs.
  • A diagnostic run never forms a verdict. The next census on the repaired head decides.
  • A census whose every red is machine-classified INFRA or INCOMPLETE is re-dispatched once automatically; both runs stay reported.
  • An unresolved red is listed as a named red with its diagnosis. A case enters CASE_QUARANTINE only under its entry rule, and a product defect is never quarantined.