check
Evaluate a work item's process-graph nodes against its checked-in artifacts, and report what is unmet.
the-loop check <work-item> [--repo DIR] [--spec-dir docs/specs] [--format table|json]
[--recompute] [--fail-on unmet|block]
the-loop check --all [--repo DIR] [--spec-dir docs/specs] [--format table|json]<work-item> is the spec-directory id (issue-117) or the work item's ref (github:OWNER/REPO#117), which names the same directory the daemon writes (issue #396).
--spec-dir names where the checkout keeps its specs; unset, the CLI config's routing.graph.specDir answers, else docs/specs. The CLI reads no harness config (issue #352), so a repository laid out differently says so on the command line — the loop's convention is docs/specs, and routing.graph.specDir or this flag is for an instance that departs from it.
$ the-loop check issue-117
issue-117: ok (at brainstorming)
state: /work/the-loop/docs/specs/issue-117/work-item-state.json
···· 7 node(s) not reached yetThe state: line names the work-item-state.json the answer came from — found or not. A work item that has not been driven through the graph sits at the start node with everything ahead of it "not reached yet" — which is ok, not a failure — and its line reads (not found — the work item has not entered the graph; reporting its start node). When the work item's directory is not there at all the line says so and points at --repo: the answer above it is the graph's default, not a recorded position. When something at or before the pointer is unsatisfied, it is named with its status and its reasons:
issue-42: UNMET (at design-approval)
WAIT design-approval
· waiting on a human gate
···· 4 node(s) not reached yetIt is pure
No network. No subprocess. No mutation.
That is what lets the same code run on every harness turn and in CI — so the gate is the runtime itself, rather than a reimplementation of it that drifts from it. It is also why check is safe to run in a loop, on someone else's branch, or on a work item you know nothing about.
Flags
| Flag | Default | Meaning |
|---|---|---|
| (positional) | — | Work item id (issue-117) or ref (github:OWNER/REPO#117). Give this or --all. |
--repo | unset | Repository root. Unset: the current directory — or, for a ref the current directory does not hold, the checkout the session registry records for that work item's session (printed as repo: … (from the session registry)). Given, it is used verbatim. |
--all | off | Evaluate every work item under the spec root and report drift. |
--format | table | table or json. |
--recompute | off | Ignore stored work-item state; derive the verdict from the artifacts alone. |
--fail-on | unmet | What makes the process exit non-zero — see below. |
--fail-on: asking versus gating
The two modes exist because "where does this stand?" and "should CI go red?" are different questions.
unmet (default)
Anything not satisfied, a human-wait included. What you want when you are asking where a work item stands.
block
Only a node an agent can actually fix. What an automated gate wants.
A work item parked at a human-approval node is the normal state of an open PR. Failing CI for that would make the gate red by construction — and a gate that is always red is one people learn to merge past.
the-loop check "issue-$(git branch --show-current | grep -oE '[0-9]+$')" --fail-on blockA repository that is not there fails both modes
$ the-loop check issue-1 --repo /typo --fail-on block
issue-1: UNREAD — /typo is not a directory
$ echo $?
1Ahead of either rule above, because it is not a verdict about the work item: nothing was evaluated. The control-plane API deliberately answers a repo that does not resolve with 200 and no position — a checkout somebody cleaned up is expected state on that machine (issue-238) — and a report with no nodes has no blocking node either. Without this rule a mistyped --repo would take --fail-on block straight to exit 0. A gate that evaluated nothing has not passed.
--recompute
Normally check trusts the stored work-item state where it has one. --recompute throws that away and derives the verdict purely from what is on disk.
Use it to answer "is the recorded state still true?" — after a graph force, for instance, which moves the pointer without forging the bypassed gate's verdict. A forced work item reads as satisfied under graph status and still reports the real gate under check --recompute.
Under --recompute the position is derived too: (at <node>) is the first node the artifacts leave unmet. That can be ahead of where the work item actually is — every node before it passes, but nothing has advanced the item into it yet. When the recorded pointer and the derived position disagree, the state: line names both (issue-429):
issue-1: UNMET (at design)
state: …/docs/specs/issue-1/work-item-state.json (pointer at phase-selection; position derived from the artifacts)The harness stop gate reads both for the same reason: it takes every verdict from the recomputed report, and never asks about a node past the pointer.
Reading the output
Nodes are split at the pointer, deliberately:
- Nodes at or before the current node are findings —
ok,--(skipped),wait(blocked on a human) orBLOCK(an agent can fix it). - Nodes beyond it are summarised as "not reached yet". A node the work item has not got to is expected to be unmet; reporting it in the same voice as a genuine blocker is how a status view starts contradicting itself, printing
okabove a wall ofBLOCKlines.
--all prints one line per work item plus the first finding for each, and a n/m work items satisfied summary — and no state: lines: it is a drift summary.
With --format json the report carries statePath and stateFound beside its nodes, and pointer — the node that state file records, "" when none was found. Without --recompute it equals currentNode whenever a state file was found.
An archived work item
A work item that has ended — its ticket closed — and whose checkout normal cleanup has removed has no work-item-state.json anywhere check can look. Given its ref, check then answers from the terminal record the closure kept in the portable record (issue-452) instead of evaluating the directory you are standing in:
issue-3: ARCHIVED — completed (at complete)
state: /srv/devbox/docs/specs/issue-3/work-item-state.json (not found; reporting the terminal record this machine kept when the work item ended — issue-452)
ended: closed by octocat at 2026-10-02T22:30:00Z (issue-closed)
completed: 2026-10-02T22:29:41+00:00
selections: skipped brainstorming, requirements-definition, requirements-approval, design, test-planning, design-approval, tasks-breakdown
choices: harness codex
pull request: github:octo/repo#4 (merged) https://github.com/octo/repo/issues/4
evidence: docs/specs/issue-3/evidence/ — verification.mdThe outcome is completed only when the work item's session claimed its completion node; an issue closed as not planned or as a duplicate (or a pull request closed unmerged) is cancelled, any other closure closed-externally. A stamp with no terminal record — written before this change, or when the checkout could not be read — prints ARCHIVED — unknown and says the archived detail is unavailable. There are no node findings: the directory you ran from is not the work item's checkout. --fail-on block exits 0 for an archived item, and --fail-on unmet exits 0 only for a completed one.
The archive is consulted only for a ref (the portable record is keyed by ref), only when no state file was found — a checkout that has one always wins — and not under --recompute when the work item's spec directory is present. With --format json the report carries archived (ref, outcome, detail, state, reason, at, actor, and terminal when recorded), nodes: [], and currentNode/pointer set to the recorded node.
Exit codes
| Code | Meaning |
|---|---|
0 | Nothing failing, per --fail-on |
1 | At least one work item fails, per --fail-on |
2 | Could not run — no work item given, or the runtime could not be assembled |
See also
graph— inspect and drive the same runtime.- process-graph — nodes, hooks, edges, the human gate.
- CLI config — the spec directory and the hooks
checkreads.