Blocking and the unlock
A critical finding blocks the merge at once, through a check run nobody can dismiss. Who may lift it, and how.
The verdict comes from the tool, not from the model
The agent chooses which of two tools to call. Which tool it called is what makes a review advisory or blocking — so a model cannot talk its way to a softer verdict after the fact, and nothing waits for a person before it posts.
| Situation | What posts |
|---|---|
No critical finding | A comment review, with the findings and the inline comments. |
Any critical finding | REQUEST_CHANGES, at once. A broken test, a probe that contradicts the diff, a decoy secret read, an install that called an unknown host: all of them block the same way. |
Cujo never posts APPROVE. It cannot satisfy branch protection, so it can never wave a bad merge through by staying quiet.
The lock is a check run
A review can be dismissed by anyone with write access — a coding agent with write access included. A check run cannot be dismissed at all: only the App that owns it can complete it. So beside every review, Cujo writes a check run named cujo/guard on the commit it reviewed, and moves it as the run moves: in progress from the moment the commit is claimed, success on a clean run, failure on a block.
To make the block hold the merge, require the check in branch protection: Require status checks to pass, and pick cujo/guard. The check appears in the picker once the App has written one. Without that setting a block is still a REQUEST_CHANGES review and a red mark on the commit, and nothing more.
Checks: write permission on the App, which every installation has to approve once. Until it has, the review and the reaction post as before and the check is not written.The unlock, on the pull request
A block is lifted by a person, and only by a person. Two verbs exist, each alone on its own line in a comment. They are matched as exact strings by the service and never by a model, and a line inside a code fence, a blockquote or an HTML comment does not count — if a reader cannot see it, it is not a command.
| Command | Does | Who may |
|---|---|---|
| /cujo dismiss | Dismisses Cujo’s blocking review on the current commit and turns the check neutral, naming who dismissed it. The findings and their evidence stay on the pull request; only the block is lifted. | Anyone with write access except the pull request’s author, and never a bot account. |
| /cujo review | Reviews the current head again. Its main use is a pull request Cujo never saw; any earlier run for that commit is replaced. | Anyone with write access, the author included. |
The author may not dismiss, because that is the direction that lifts a block on their own change. A bot account may not dismiss whatever access it holds, because the unlock exists to be the one thing a coding agent cannot do to the block it earned; GitHub’s own word for the account is what decides it, before any permission is read. A new commit gets its own run, its own review and its own check, so a block is never carried forward — and never dismissed forward either.
Write access is read from GitHub on every command, and every outcome speaks on the pull request — a refusal nobody can see is indistinguishable from a delivery that never arrived. The command comment gets a thumbs up when it was applied and a confused face when it was refused.
The seven run states
| Status | Means | Reaction | Check |
|---|---|---|---|
| running | Review running: tests, probes, a smoke boot, and dependency detonation. | eyes | in progress |
| clean | No critical finding. The advisory review posted. | hooray | success |
| blocked | Blocking review posted as REQUEST_CHANGES; the cujo/guard check fails. | thumbs down | failure |
| dismissed | The block was lifted by a maintainer. The observation stands. | thumbs up | neutral, naming who dismissed it |
| error | The run ended in error. | confused | neutral |
| unproven | The review posted with no evidence: not one check returned a report. | confused | neutral |
| superseded | Replaced by a newer commit on this PR. | nothing | skipped, unless a newer run owns the commit |
- The reactions describe what happened to the pull request, not what Cujo concluded — which is why a dismissed block leaves a thumbs up even though the findings stand.
- Red is reserved for a pull request that is dangerous, never for Cujo falling over, so a run that errored is drawn in the same blue as a clean one at reduced strength.
- A superseded run writes no reaction at all. The run that replaced it is about to say what the pull request should show. Its check is per commit, so it is marked skipped — unless the replacement reviews the same commit, which then owns the check.
- A diff review reaches three of these:
running,cleananderror. Itscleanis an advisory review with findings of at mostwarn, and neverunproven, since it never had evidence to post; the run page and the record mark itdiff reviewso the two kinds ofcleanare not confused.
What each state looks like on the board is on reading the board.