CodeHerderSearch⌘KRequest access →

Stage signals

See how well each workflow stage's own gate is calling it — which attempts get accepted, reworked, or never resolved, and how the gate's own accuracy holds up.

First-pass rate and review debt tell you how often work gets sent back and how long review takes. Stage signals answers a narrower, sharper question: for a given stage, was the call the gate made actually the right one? It looks at every attempt a stage made, works out whether that attempt held up, and then checks the gate’s own track record — did it approve work that later failed, or reject work that turned out fine?

Where to find it

There’s no web-app page for this report yet — it’s CLI and API only.

ch quality stage-signals
ch quality arms

Pass a workspace name, slug, slug path, or full ID as the first argument to scope it elsewhere; without one, the command uses the workspace the CLI is pointed at. See Using the ch CLI for how ch resolves a workspace reference. Add --json to get the raw data instead of the table.

Any member of the workspace can run this report — there’s no extra role or plan requirement.

Choosing a window

The CLI takes --window today, --window yesterday, --window week, or --window month. Without a --window or --since, the report covers the last 90 days.

For a custom range, use --since instead, with a relative duration (3d, 12h, 45m) or a full RFC 3339 timestamp — the same grammar as ch costs --since. --since and --window are mutually exclusive. Unlike composition outcomes, --since here has no 90-day limit — you can reach back as far as you need.

This report always covers exactly the workspace you asked for, with no cascade into child workspaces.

What each number means

The report prints two tables, one row per stage.

Attempt outcomes

Every time a stage’s work gets attempted, that attempt eventually lands in one of four buckets:

Outcome What it means
ACCEPTED The attempt moved past its stage with no sign anything needed fixing.
REWORKED Something pointed back at this attempt — a failed check, work sent back into the same stage, a rejected advance, or an escalation.
PENDING The attempt hasn’t moved past its stage yet, and nothing has flagged it either.
CENSORED The task was cancelled, archived, or otherwise abandoned before the attempt moved past its stage, with no sign anything needed fixing.

Censored is never counted as accepted. An attempt that never got a clean pass shouldn’t read as one just because the task itself ended before anyone found out — CENSORED exists so an inconclusive attempt stays visibly inconclusive instead of padding your acceptance numbers.

The table’s columns:

Column What it tells you
STAGE The workflow stage this row covers.
ACCEPTED Attempts that came out clean.
REWORKED Attempts that needed a second look.
PENDING Attempts still in flight.
CENSORED Attempts cut short by the task ending.
DECIDED ACCEPTED plus REWORKED — the total attempts with a real verdict, and the denominator for an acceptance rate.
MEDIAN-REVEAL The median time between an attempt leaving its stage and CodeHerder confirming whether it held up.
CONFOUNDED Of the attempts already counted in ACCEPTED or REWORKED, how many had more than one gate sit between them and the point where they were confirmed — so you can’t tell which gate actually caught the problem. Not a separate bucket: every confounded attempt is also counted in ACCEPTED or REWORKED.
MIXED-ARM-EXCL Attempts held out of every other column in this row, because more than one model or agent configuration touched the work, so no single configuration can take credit or blame.

Review accuracy

For a stage that acts as a gate — one that can approve or reject work — the second table checks whether its calls held up:

Verdict What it means
AGREED The gate approved and nothing later found a problem, or it rejected and the next attempt changed something.
FALSE-NEG The gate approved, but a later stage caught a problem, or work got sent back to at or before the stage it approved out of.
FALSE-POS The gate rejected, but the next attempt changed nothing meaningful — the rejection didn’t lead anywhere.
UNKNOWN There isn’t enough evidence yet to say either way.

UNKNOWN isn’t part of this table’s DECIDED count. DECIDED is AGREED plus FALSE-NEG plus FALSE-POS — the calls stage signals could actually judge. UNKNOWN sits outside it on purpose, so a stack of open questions never gets averaged away into an accuracy rate.

The table’s columns:

Column What it tells you
STAGE The gating stage this row covers.
AGREED Calls that held up.
FALSE-NEG Approvals that turned out wrong.
FALSE-POS Rejections that turned out wrong.
UNKNOWN Calls with no evidence yet either way.
DECIDED AGREED plus FALSE-NEG plus FALSE-POS.

Reading the table

MEDIAN-REVEAL prints an em dash (), never 0, for a stage with no confirmed attempts yet — so “nothing to measure” never gets mistaken for “instant.” If the window has no data at all, the report prints (no data).

Sample output, with made-up numbers:

ch quality stage-signals — window Jul 24 00:00 → Aug 23 00:00

  STAGE   ACCEPTED  REWORKED  PENDING  CENSORED  DECIDED  MEDIAN-REVEAL  CONFOUNDED  MIXED-ARM-EXCL
  code    58        14        3        1         72       2h10m0s       2           1
  review  71        9         0        0         80       18m0s         0           0

  STAGE   AGREED  FALSE-NEG  FALSE-POS  UNKNOWN  DECIDED
  review  62      4          5          9        71

Read this before you act on it

A stage with a low ACCEPTED count isn’t necessarily a stage doing bad work — check PENDING alongside it first. A stage where a lot of attempts are still in flight will always look thin on ACCEPTED and REWORKED until those attempts get confirmed one way or the other; that’s slow reveal, not a quality problem.

CONFOUNDED and MIXED-ARM-EXCL both flag attempts you can’t cleanly attribute, but they work differently, and mixing them up will throw off your arithmetic. MIXED-ARM-EXCL is held out of every other column — those attempts aren’t counted in ACCEPTED, REWORKED, PENDING, or CENSORED at all, because more than one model or configuration touched the work and none of them can be credited or blamed alone. CONFOUNDED is not an exclusion: it’s a flag on attempts already sitting inside ACCEPTED or REWORKED, marking that several gates sat between the attempt and its confirmation, so you can’t tell which one actually caught anything. A stage with high CONFOUNDED still has a real ACCEPTED and REWORKED count — you just can’t credit this stage alone for it.

Which setup did the work

ch quality stage-signals groups attempts by stage only. ch quality arms breaks each stage down further, by which setup ran it — the harness, the model, and the exact stage prompt used.

ch quality arms
ch quality arms — window Jul 24 00:00 → Aug 23 00:00

  STAGE   CLASS  HARNESS  MODEL   STAGE-HASH  RATE  ACCEPTED  REWORKED  DECIDED  PENDING  CENSORED  CONFOUNDED  FULL-ARMS
  code    bug    claude   sonnet  a1b2c3d4    92%   46        4         50       6        1         1           3
  code    story  codex    gpt     e5f6a7b8    78%   14        4         18       2        0         0           1

  excluded: 5 unsettled, 2 mixed-arm, 0 arm-unknown

Each row is one cell: a stage, a task class (the kind of task — bug, story, and so on), and a routing setup — the harness, the model, and a short hash standing in for the exact stage prompt used. RATE is ACCEPTED divided by DECIDED, the same acceptance rate stage-signals reports, just narrowed to this one setup. RATE prints an em dash (), never 0%, for a cell with no decided attempts yet.

A cell is one stage attempt’s own verdict — never the whole task’s later reject history. If a task gets rejected three stages after this one, that rejection does not move this cell’s rate. Each row only counts what happened at its own stage.

FULL-ARMS is how many exact setups this row actually covers. The STAGE-HASH groups by stage prompt, harness, and model, but two runs with the same three can still differ underneath — a different reasoning-effort setting, a different launch configuration, or a different set of skills loaded. FULL-ARMS counts how many of those exact combinations landed in this row, so a 1 means the row is one setup through and through, and anything higher tells you the row is a blend. To see the exact setup behind any one attempt, run ch quality attempts and read that attempt’s own ARM value.

The excluded line below the table counts attempts this report could not place in any row: unsettled attempts still waiting on a verdict, mixed-arm attempts where more than one setup touched the work, and attempts whose setup could not be identified at all. If the workspace has more distinct cells than the report can show, it also prints a line naming how many got dropped.

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch the work move. Costs update as it happens.

Try "pricing", "connect a device", or "who reviews the code"

↑↓ move · ↵ open · esc close