Skip to content

Repair the GitHub code-review onboarding - #473

Merged
TheGreatAxios merged 5 commits into
mainfrom
cl-7189-github-code-review-repair
Aug 30, 2026
Merged

TheGreatAxios merged 5 commits into
mainfrom
cl-7189-github-code-review-repair

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Driving the Code review template end to end: the connect card said "Connect GitHub" over a connection that had already stored a credential, the walkthrough described a permission model we do not have, and the reviewers answered in raw JSON. Four defects, each with its own commit.

Fixes CL-7189.

The connect kept failing

listRepos decorated every repo row with an exact open-PR count from /search/issues — one call per repo, fired concurrently off Promise.all. GitHub allows 30 search requests a minute and secondary-rate-limits bursts, so any account past a handful of repos got a 403, the whole listRepos rejected, and readGithubState returned {ok:false}, leaving the card in its error state. Reconnecting hit the same wall.

The picker now reads entirely from the one /user/repos call. The count is gone; pushed_at rides along on that response for free and explains the ordering, so a row still says something ("updated 2h ago") without a second request.

A failed read looked like "never connected"

ConnectGithubBlockContainer's mount effect had no rejection path, so a read that threw left the query on loading — which renders the disconnected body. That is how a working connection ends up under a Connect button. A rejection now becomes the card's own error state, through reportError with a quotable refId.

The steps described a GitHub App

Connect GitHub → Pick your repos → Start reviewing, with the picker's helper calling each repo "its own permission you can turn off later", is the GitHub App flow, where repo selection happens after install. With a PAT the access is settled while the token is created, and picking a repo afterwards grants nothing.

Per the owner ruling — PAT only, App stays out of scope — the connect step now walks through a fine-grained token scoped to the repositories you want reviewed, and step two is honestly a watch-list over what that token already reaches: "Choose what gets reviewed", with narrowing pointed back at GitHub.

Reviewers posted JSON into the room

REVIEWER_REPORT_CONTRACT ("Reply with JSON and nothing else") was concatenated into every ReviewerDefinition.systemPrompt, and those prompts are what installs the reviewers as chat agents. The contract was also inside the review workflow's prompt, contradicting its own instruction to write one prose review and post it through github_post_pr_review.

A definition now carries its lens alone; reviewerReportPrompt appends the contract on the single path that parses JSON back, and ReviewerTurn receives the system prompt to use rather than reaching for the definition's.

Checks

bun run check passes apart from @corbits/tool-registry-publish, whose five failures reproduce identically on main — its fixtures commit as freshness@test, which a local git hook's author allowlist rejects.

Listing repos cost one `/search/issues` call per repo, fired
concurrently, purely to decorate each row with an exact open-PR count.
GitHub allows 30 search requests a minute and secondary-rate-limits
bursts, so any account past a handful of repos got a 403, `listRepos`
rejected, and the connect card fell into its error state — every time,
so the connection looked permanently broken.

The picker now reads from the one list call it was already making. The
open-PR count is gone; the push timestamp that explains the ordering
rides along on that same response for free.
The walkthrough read Connect GitHub, then Pick your repos, and the
picker's helper called each repo "its own permission". That describes a
GitHub App, where repo selection happens after install. We ship a
personal access token: repo access is decided while the token is being
created, and picking a repo afterwards grants nothing.

The connect step now walks a person through a fine-grained token scoped
to the repositories they want reviewed, and the picker is honestly a
watch-list over what that token already reaches — "Choose what gets
reviewed", with narrowing pointed back at GitHub where it really
happens.
The card's mount effect read its state with no rejection path, so a read
that threw left the query on `loading` — which renders the disconnected
body. A person with a working GitHub connection was told they had never
connected one, with a Connect button that could only lead them through
the same failure again.

A rejected read now becomes the card's own error state, reported through
the error sink with a refId, and says plainly that GitHub could not be
reached with this token.
Each reviewer's system prompt ended with "Reply with JSON and nothing
else", and those same prompts install the reviewers as ordinary chat
agents. Asking one of them anything in a room got a raw
`{"summary": ..., "findings": []}` back. The same contract also sat
inside the review workflow's own prompt, where it contradicted the
instruction to write one prose review and post it through the GitHub
tool.

A reviewer definition now carries its lens alone. `reviewerReportPrompt`
appends the contract on the one path that parses JSON back — the review
run's pass turns, which now receive the system prompt to use rather than
reaching for the definition's.
"6 your token can reach · 3 picked" was missing its subject.
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Reviewed against CL-7189's four defects and the diff.

Verified against acceptance criteria — all satisfied:

  • listRepos (packages/github-tools/src/repos.ts) now reads the picker from the single /user/repos list call; the per-repo /search/issues burst that triggered GitHub's secondary rate limit is gone. lastPushedAt rides along for free from pushed_at.
  • ConnectGithubBlockContainer's mount effect routes a rejected state read through readConnectState, which reports via reportError and returns a spoken error state instead of leaving the query on loading (which rendered as "you haven't connected yet").
  • Onboarding copy (templates.ts, strings.ts) no longer frames repo picking as a permission boundary — it's honest about a fine-grained PAT: access is chosen while creating the token, the picker is a watch-list over what the token already reaches.
  • REVIEWER_REPORT_CONTRACT is out of ReviewerDefinition.systemPrompt; reviewerReportPrompt() appends it only on the one path that parses JSON back (review-run.ts). An installed reviewer agent now answers a person in prose.

Ran the full touched-file test set (chat-ui, chat, code-review, github-tools, workflow-catalog, last-30-days-research) — all green. bunx prettier --check and bun run lint clean on touched files. check:structural clean, including tool-package-pins/tool-package-freshness picking up the @corbits/github-tools 0.0.8→0.0.9 bump correctly across both workflow pins.

Commit messages: no prefixes, no tracker refs, all subject/body lines within 72 chars.

No findings. No code changes needed.

@TheGreatAxios
TheGreatAxios merged commit 97c1777 into main Aug 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant