The check run and JSON
The check run is written for two readers: a person who scans the title, and an agent loop that reads the JSON and fixes the findings without a human relay.
The check is named crocotaste and is posted by the Crocotaste GitHub App, so an agent can find it by name and verify it by app id.
Titles, by state
| State | Title | Conclusion |
|---|---|---|
| Pass | On-system · 6 files · 42 tokens · 12 components · 9 decisions · 0 violations | success |
| Pass, checks only | On-system · checks only · 6 files · 42 tokens · 12 components · 9 decisions · 0 violations | success |
| Findings, drift only | 1 finding · 1 drift | success |
| Findings, a violation | 2 findings · 1 violation · 1 drift | neutral |
| Neutral (our failure) | Couldn't complete · re-run with @crocotaste review | neutral |
| Neutral (balance) | Review budget used · top up or upgrade | neutral |
| Skipped | Skipped · no UI files changed | neutral |
| Skipped (too large) | Skipped · too large to review | neutral |
| Skipped (no system) | Skipped · no design system found | neutral |
| Skipped (outsider) | Skipped · ask a member for @crocotaste review | neutral |
| Running | Reviewing 6 files… ~60s | none yet |
The pass is always enumerated in the same order: files · tokens · components · decisions · violations. The number you trust is in the same position every time.
A check is only ever success or neutral, never failure, so a red check on your pull request belongs to somebody else. Green means on-system: a pass, or drift alone. A violation turns the check neutral until it is fixed or dismissed. Neutral never blocks a merge, even where you require the check, and findings are posted as a comment review, never request-changes: what blocks a merge stays your branch protection.
The JSON
A completed check's output.text ends with a fenced JSON block. Two checks carry none: the in-progress one, which has no body, and the Couldn't complete one, which publishes nothing rather than an empty result an agent might act on.
{
"state": "findings",
"grounding": {
"hasDesignMd": true,
"files": 6,
"tokens": 42,
"components": 12,
"decisions": 9
},
"ai": "ok",
"withheld": 0,
"findings": [
{
"verdict": "VIOLATION",
"path": "src/app/page.tsx",
"line": 12,
"message": "`#4ade80` is `--color-success` — use the token (tokens.css:18)",
"citation": { "file": "tokens.css", "line": 18 },
"fingerprint": "3f2a…"
}
]
}
| Field | Meaning |
|---|---|
state | pass, findings, neutral, skipped or too-large; the check's own state, not the pull request's |
grounding | what the review was checked against. files counts UI files reviewed, not files changed; hasDesignMd false means the system was parsed from code alone |
ai | what the model did with this push: ok (it read the diff), skipped (nothing new for it: the same lines as its last read, every line already reported, or an empty inventory; with the pull request's AI findings already spent, only when its lines are the ones last read), withheld (a read was due and did not happen: the review's included reads spent, an empty balance, two failed answers, or the pull request's AI findings already spent with new lines no model read; the pass is the checks' alone and carries no approval), failed, malformed or disabled; a skipped check carries skipped and the budget-used check withheld. withheld here is the model's status; the top-level withheld is the count of AI findings the cap held back |
withheld | AI findings the per-pull-request cap of 3 held back |
findings | the findings listed on this push, violations first |
verdict | VIOLATION or DRIFT |
path, line | the added line in the new file, the same anchor as the inline comment |
citation | the file and line in your references that the finding is proved against |
fingerprint | a stable hash, the same marker embedded in the inline comment |
state: "neutral" covers both neutral titles, so an agent that needs to tell an exhausted balance from a failure should read the title. The wording of a failure is free to change; the states are not.
What an agent should do with it
Match findings to comments by fingerprint. It is stable across force-pushes and line shifts, and it is what stops a fix loop acting twice on one finding.
Re-read the check after your fix lands. findings is what was posted on this push, not everything known; when more exist, the summary above the JSON block says how many are not yet listed, and they post on later pushes as the listed ones are fixed or dismissed. An agent that never re-reads will believe it finished a batch early.
Treat withheld as work, not noise. Those findings were real; they were held back only because three per pull request is the ceiling.
Do not parse the message for structure. It names the value, then the token or component, then the source, but it is prose. citation and path/line are the structured fields.
Findings and verdicts explains what each verdict means and where a finding comes from.
Reading it
With the GitHub CLI, from inside the repository; gh fills in {owner} and {repo} itself:
gh api "repos/{owner}/{repo}/commits/$SHA/check-runs" \
--jq '.check_runs[] | select(.name == "crocotaste") | .output.text' \
| awk '/^```json$/{f=1;next} /^```$/{f=0} f'
That prints the JSON object alone, ready to pipe into jq. An empty result means the check has not completed yet, or it is the Couldn't complete one; when a review fails lists every state a check can end in.