Skip to content

Scripts and orchestrators

boo --json prints one JSON array, with one object for each pull request, in the order of the list. It posts nothing and merges nothing. It has no colors, no spinner and no width limit, so a script needs no COLUMNS and no clean-up. For a stream of changes, use --watch --events.

{
"repo": "acme/web",
"number": 921,
"title": "feat(search): filter the results by date",
"url": "https://github.com/acme/web/pull/921",
"headSha": "4f1c2e…",
"base": "main",
"isDraft": false,
"state": "checks failing",
"stalled": false,
"idleMinutes": 12,
"mergedBy": null,
"mergedAt": null,
"error": null,
"checks": { "passed": 8, "failing": ["Migrations"], "running": 0, "skipped": 1, "quota": 0 },
"reviewers": [
{
"login": "greptile-apps[bot]",
"mark": "commented",
"onHead": true,
"verdict": "commented",
"score": { "score": 4, "total": 5 },
"at": "2026-10-24T09:12:00.000Z"
}
],
"stack": { "parent": null, "position": 1, "size": 2, "restack": null },
"agent": {
"kind": "claude",
"session": "web-2",
"subAgent": {
"id": "a0e40d0b237c6058e",
"description": "Fix the search filter",
"state": "working",
"error": null
},
"sessionState": "working",
"cloudSession": null
},
"notes": {
"merge": "blocked by the branch protection rules",
"retries": [{ "bot": "Copilot", "at": "2026-10-24T10:00:00.000Z" }],
"coderabbit": null
},
"policy": [],
"stale": null
}
Key Value
state The words of the human output: conflicts, changes requested, checks failing, ready to merge, approved, reviewing, reviewed, quota, awaiting review. Also merged, closed or draft for a pull request that left the list, and error if GitHub did not answer (see error).
stalled, idleMinutes stalled is true for a stalled pull request (see Stalled pull requests). idleMinutes gives the minutes since the commit time of the head commit, or null.
mergedBy, mergedAt The login that merged the pull request (a GitHub App ends with [bot]), and the time of the merge. null if it is not merged.
headSha, base The head commit and the base branch. null if GitHub did not answer.
checks The counts of the checks of the head commit, and the names of the checks that fail. quota counts the checks of a reviewer that refused for quota: they do not fail.
reviewers[].mark approved, changes requested, commented, reviewing, quota, to retry, requested, stale (an older commit only), skipped or paused.
reviewers[].onHead true if the last review of the reviewer covers the head commit.
reviewers[].verdict The verdict that stands over all the commits, or null.
reviewers[].score The last confidence score of the reviewer, or null.
reviewers[].at The time of the last review of the reviewer, or null.
stack The listed pull request that this one merges into (owner/repo#number), its place from the bottom (null if the stack forks), and the size of the stack. restack is { "oldBase", "at" } if the pull request needs a rebase after its parent merged (see Stacks), else null.
agent.kind claude, copilot, codex, cursor, devin or null. A local session makes it claude.
agent.subAgent The sub-agent of the session that changed the pull request last, as { "id", "description", "state", "error" }, or null. state is working, done, failed or stopped; error tells why a failed sub-agent stopped, for example rate limit. See Sub-agents.
agent.sessionState working, idle, waiting, or none if no session on this machine linked the pull request.
notes.merge The rule that blocks the merge, or null.
notes.retries The retries of the review bots that a run without --json posts, with their times.
notes.coderabbit The CodeRabbit quota: wait with availableAt, trigger, pending, skipped, paused, idle, or null.
stale { "at", "until" } if GitHub refused the query for a rate limit: the other keys then give the last read that worked, at at, and until is the time that GitHub answers again (or null). null for a read of now. See Quotas and retries.
policy The rules of the merge policy that the pull request misses, as { "rule", "note" }: minScore, requireProof, resolvedBotThreads, ownerOnly, answeredReviewRequests. Empty with no policy.

The keys stay the same from one version to the next. A test fixes them.

boo --watch --events reads the list once a minute and prints each change as one JSON line (NDJSON), so a script follows all the pull requests with no diff of its own. It posts nothing and merges nothing. Each line has at, pr (owner/repo#number) and event:

{"at":"…","pr":"acme/web#480","event":"state","from":"approved","to":"checks failing","failing":["Migrations"]}
{"at":"…","pr":"acme/web#480","event":"check","check":"Migrations","to":"failing"}
{"at":"…","pr":"acme/web#480","event":"review","reviewer":"sakuga-review[bot]","mark":"approved","onHead":true,"verdict":"approved","score":{"score":4,"total":5},"reviewedAt":"…"}
{"at":"…","pr":"acme/web#480","event":"left","reason":"merged","mergedBy":"octocat","mergedAt":"…"}
event When Other keys
snapshot The first read, one line for each pull request The keys of --json
entered A pull request enters the list The keys of --json
state The state changes from, to, failing, and error with the message of GitHub if to is error
head A new head commit from, to
review A reviewer has a new mark, verdict, score or review reviewer, then the keys of reviewers[] in --json, with reviewedAt for at
check A check starts to fail, or stops (it passed, or it runs again) check, to: failing or not failing
left A pull request leaves the list reason: merged, closed, draft, or unlisted if it is still open; for merged, also mergedBy and mergedAt
restack GitHub moved the pull request to a new base after its parent merged oldBase, base, parent: the pull request that it was stacked on, or null if it was not listed
comment A person writes a comment, a review or a line comment author, createdAt, body (the first 200 characters), thread: true for a line comment

A comment event comes from a person, so that the agent of the pull request reads it at once. A bot, the author of the pull request (an agent often posts as the author) and the logins of commentEvents.ignoreLogins in the settings file give none. A comment that contains a marker of commentEvents.ignoreMarkers gives none either: an agent can post with the account of a person, and mark its replies. The first read of a pull request gives no comment event. The watch reads the text of the line comments only with --events: the first 10 of each review.

{ "commentEvents": { "ignoreLogins": ["my-agent-account"], "ignoreMarkers": ["<!-- claude-reply -->"] } }

GitHub fails one read now and then. A pull request goes to error only after two failed reads in a row: until then it keeps its last state, and gives no event. Then its line says "to":"error", with the message of GitHub in error, and the next read that works says "from":"error". A list that GitHub does not give writes an error on stderr, and the watch goes on.