Skip to the content.

bees review reviews one GitHub pull request, any pull request your gh login can read. It runs without a factory: it reads no bees.toml, no state directory and no mailbox. Several read-only agent sessions review the change, each from one angle, bees merges what they found into one list, and you decide about each finding before the review ends.

cd ~/src/widgets
bees review 7

The commands and their flags are in the CLI reference. This page covers what a review does, where it keeps what it found, and the two files that configure it.

How a review runs

context  ->  brief  ->  angles  ->  findings  ->  triage  ->  end
  1. Context. bees gathers the pull request’s context from the sources context.toml enables: the diff, the pull request’s conversation, the issues it links, the project’s style files and the callers of what it changed. A source that cannot read something records what it missed, and the review goes on.
  2. Brief. One session, the distiller, reads everything gathered and writes the brief: what the change does, its size (xs, s, m, l or xl, judged from how many files and lines it touches and which parts of the project those are), its acceptance criteria with the issue or text each came from, the style rules that apply to it with the file each is written in, the parts of the project it touches, and what the sources could not read.
  3. Angles. One session per angle the brief’s size calls for and context.toml enables reads the brief and the diff, and answers with findings. The angles run at the same time, and at a terminal the command shows each one’s progress while they do. An angle that fails is skipped; the review stops only when every angle fails.
  4. Findings. bees merges the angles’ answers into one list, in code and not in a session (see Findings), then applies the rules in your reviewer notes: a finding you dismissed before is dropped or ranked down.
  5. Triage. You take each finding in turn and select, dismiss, defer or ask about it. With --agent, an agent session does it instead: see factory mode.
  6. End. What you selected is posted as one review, printed as a report, or discarded: see How a review ends.

Every session is read-only. A claude session may use Read, Grep, Glob, LS and NotebookRead and nothing else; a codex session runs in Codex’s read-only sandbox. No session runs the tests, builds the change or writes to the repository.

Before the angles run, bees checks out the pull request’s head for them to read, in a container: an Alpine image with git, built the first time and kept, clones refs/pull/<number>/head of the pull request’s repository over HTTPS into the review’s checkout/ directory and exits. That is the head as it is on GitHub, whatever your working tree has checked out, and a fork’s pull request as much as one from a branch of the repository. The angle sessions run in that directory, on your machine, with the same read-only tools; nothing runs in the container after the clone. github.token authenticates the clone when it is set, handed to the container as an environment variable; without it the clone is anonymous, which a private repository refuses.

The checkout needs docker. Without it, or when the image does not build or the clone fails, the review says so and goes on: the angles then run in the current directory when it is a checkout of the pull request’s repository (its origin remote points at it), and in the empty scratch/ directory of the review anywhere else.

The other sessions run where the angles would without that checkout: the distiller in the current directory when it is a checkout of the repository and in an empty temporary directory anywhere else, and the triage sessions of factory mode in the current directory or in scratch/. The sources that read files (style_files, callers and the project’s own) read the current directory when it is that checkout and gather nothing anywhere else.

Context sources

Source What it gathers
diff The pull request’s diff
pr_body The title and body, then every comment and submitted review, oldest first
linked_issues The issues the pull request closes, then the others its body mentions, at most 20
style_files AGENTS.md, CLAUDE.md, CONTRIBUTING.md, STYLE.md and .editorconfig, plus the files style_sources names
callers The lines outside the changed files that name a function the diff declares: the first 20 functions, 40 lines each

Every one of them runs unless context.toml turns it off, and a project adds sources of its own that gather the files they name. See context.toml.

Angles

Angle What it looks for
quick_general One light pass over the change as a whole, for what a careful reader catches on one read
general A thorough pass over the change as a whole: wrong logic, a dropped error, duplicated code, and a line that breaks a rule the project wrote down
docs A comment or doc comment the change made false, and prose the change wrote that the code does not bear out
test_coverage Behaviour with no test on it, a test that would pass with the change undone, and a document the change made false
acceptance_criteria A criterion from the brief the change does not meet, or meets only in part, and a behaviour change nothing asked for
side_effects A caller the change did not update, an invariant it no longer keeps, and a claim elsewhere in the repository it made false

The brief assigns the size automatically, from the scope and the risk of the diff, and a review runs the angles that size calls for:

Size Angles
xs, s quick_general, docs
m, l general, docs, test_coverage, acceptance_criteria
xl general, docs, test_coverage, acceptance_criteria, side_effects

A project’s angles.<angle> entry in context.toml still turns an angle off entirely; the size only narrows what is left on, and never re-enables an angle the project turned off.

An angle session is also told the rules in your reviewer notes about its angle, so it reports less of what you have dismissed before.

Findings

A finding is one problem, as the review keeps it in findings.json:

{
  "id": "1a2b3c4d",
  "angle": "test_coverage",
  "session_id": "5f0c...",
  "category": "missing test",
  "severity": "high",
  "file": "internal/review/gather.go",
  "lines": [12, 14],
  "side": "new",
  "title": "Gather has no test for a source that cannot read",
  "body": "The acceptance criterion says a source that cannot read ...",
  "evidence": "gather_test.go covers only sources that succeed",
  "sources": ["#566"]
}

file, lines and side are absent on a finding about the change as a whole. side is new for the file as the change leaves it and old for lines the change removed. suggestion, present when the angle could write it, is the text that would replace the lines.

The merge is the same for every review:

Reviewer notes

Your reviewer notes are one markdown file, reviewer-notes.md next to your config.toml unless notes_path says otherwise. Every finding you dismiss during triage appends a line to it, with the reason you gave:

- [acme/widgets] [general] [naming] receiver names are short here

bees review consolidate turns the dismissals that repeat into rules, written between two markers:

<!-- bees:review:rules -->
- [acme/widgets] [general] [naming] drop: receiver names are short here (3 dismissals)
- [*] [test_coverage] [*] downrank: generated files carry no tests (2 dismissals)
<!-- /bees:review:rules -->

A pattern dismissed twice becomes a downrank rule, three times or more a drop rule. Each review applies the rules to its list before triage: a matching finding is dropped, or ranked down one severity. * matches any repository, angle or category, and a rule with no text after the action matches every finding in its repository, angle and category. You can edit a rule, and consolidation keeps your wording and changes only its count. See bees review consolidate.

Triage

Triage takes the findings one at a time, most severe first:

Action What it does
select The finding goes into the review’s output, with its text as written or as you edited it
dismiss The finding is left out, and the reason is appended to your reviewer notes
defer The finding is left out of this review, and nothing is recorded
ask The angle that found it is asked a question, in its own session; a finding the answer turns up joins the queue

Every decision is written into the review’s directory as it is taken, and the latest select, dismiss or defer on a finding is the one in force. bees review triage <pr> reopens the latest review of a pull request and offers what is still undecided. See bees review triage for the keys, and factory mode for triage by an agent (--agent, --instructions).

At a terminal, triage opens as a full-screen view beside the diff, the same keys pressed without return. --no-tui, or a stdout that is not a terminal, keeps the console described there instead.

How a review ends

End What happens
approve The selected findings are posted as review comments, and the pull request is approved
comment The same comments, as a comment-only review
reject The same comments, as a review that requests changes
report The selected findings are printed as markdown, and nothing is posted
discard Nothing is posted and nothing is printed

--post approve|comment|reject or --report on the command line chooses. With neither, the output key of config.toml does, and its default, ask, asks you at the end. With --agent and ask, the agent chooses.

The three that post submit one review in one gh api call. A finding whose lines are all in the diff is a comment on them, with its suggestion as a suggestion block on the new side. Any other finding goes into the review’s summary, with where it points. comment and reject with nothing selected are refused; approve with nothing selected approves without comments.

The review directory

Every review is a directory under storage_path, written as the review goes:

~/.config/bees/reviews/acme/widgets/7/20260910-150405/
  brief.json            the brief, and the distiller's session id
  angles/<angle>.json   each angle's session id, directory and answer
  checkout/             the pull request's head, cloned for the angles;
                        also holds diff.patch, for every angle to read
  scratch/              where the angles and triage ran without a checkout;
                        also holds diff.patch, on the same terms
  findings.json         the merged list, and what your notes hid from it
  triage.json           every triage decision, in order

The directory name is when the review started, in UTC. A review that stopped partway keeps what it reached. bees review triage reopens the newest directory of the pull request, and an ask resumes the angle’s session from its file under angles/. When the angles ran in your machine’s own checkout instead of one made for the review, diff.patch is not written there; an angle reads the code as the change leaves it in that case.

Configuration

bees review reads two files, and neither is bees.toml. Both are optional: a missing file loads as the defaults. An unknown key, or a value outside the ones listed, fails the command with an error naming the key.

config.toml

Your own settings: ~/.config/bees/config.toml, or $XDG_CONFIG_HOME/bees/config.toml when XDG_CONFIG_HOME is an absolute path. --config reads another file.

provider = "claude"
model = "opus"
brief_model = "sonnet"
notes_path = "reviewer-notes.md"
storage_path = "reviews"
output = "ask"

[angles]
xs = ["quick_general"]

[angle_models]
docs = "haiku"

[github]
token = "$REVIEW_GH_TOKEN"
Key Type Default Meaning
provider string "claude" The agent every session runs as: claude or codex
model string "opus" The model every session uses unless a key below names another
brief_model string model The model of the distiller session that writes the brief
angle_models.<angle> string model The model of that angle’s session
judge_model string none Accepted and checked, and has no effect: the judge is not a session
angles.<size> list the built-in list The angles a change of that size is reviewed from
notes_path path "reviewer-notes.md" Your reviewer notes
storage_path path "reviews" The directory review directories are created in
output string "ask" How a review ends without --post or --report: ask, approve, comment, reject, report or discard
github.token string none The token the review’s gh calls use; without it, your own gh login

A path is absolute, starts with ~, or is relative to the directory config.toml is in, which puts the defaults at ~/.config/bees/reviewer-notes.md and ~/.config/bees/reviews/.

angles.<size> replaces the list Angles gives that size; <size> is xs, s, m, l or xl, and the list names at least one angle. context.toml still turns angles off on top of it. Every session runs as provider: brief_model and angle_models change the model only. judge_model gives the file the shape of roles.reviewer in bees.toml, where the judge is a session; in bees review the judge is code and uses no model.

github.token takes a $VAR or ${VAR} reference, expanded from the environment, so the secret stays out of the file. A reference to a variable that is not set fails the command.

context.toml

The project’s settings, committed to the repository under review. bees looks for it in the current directory and then each parent, and only when the current directory is a checkout of the pull request’s repository. Without one, a review runs every angle its size calls for and every built-in source, and pins no category.

style_sources = ["docs/style/*.md"]

[angles]
side_effects = false

[categories]
naming = "low"
"missing test" = "high"
typo = "off"

[[context_sources]]
name = "callers"
enabled = false

[[context_sources]]
name = "architecture"
files = ["docs/architecture.md", "docs/adr/*.md"]
Key Type Default Meaning
angles.<angle> bool true false turns the angle off. <angle> is quick_general, general, docs, test_coverage, acceptance_criteria or side_effects
style_sources list of paths or globs [] Style documents the style_files source gathers on top of the built-in names
categories.<category> string none Pins every finding in the category to info, low, medium or high, or drops them with off
context_sources list of tables none Turns a built-in source off, or adds a source of the project’s own

A [[context_sources]] entry has a name, and:

Paths and globs are relative to the directory context.toml is in and must stay inside it.

A category is the angle’s own word for a kind of problem, lowercased: triage shows it next to the severity, and it is the third field of a dismissal in your reviewer notes. A pin matches it whatever case context.toml writes it in.