GitHub #
Status: draft. The adapter that exports a review to one GitHub pull
request and fetches what happens there. It follows the contract in
README.md ("integrations §N" below). The review model is in
REVIEW-FORMAT.md ("format §N"). The code is in
src/main/github/.
1. Destinations #
system |
github |
host |
github.com, or a GitHub Enterprise Server host |
locator |
<owner>/<name>/pull/<number>: owner and name of [A-Za-z0-9._-], a decimal number |
| external ids | GraphQL node ids, of [A-Za-z0-9_=-], at most 128 characters |
| API | GraphQL at https://api.github.com/graphql, or https://<host>/api/graphql; REST (for the pull request's files) at https://api.github.com, or https://<host>/api/v3 |
| viewer | viewer { login id }: viewer is the login, viewer_id the node id |
| declares | export, fetch, describe, checks (integrations §3) |
| URL | https://<host>/<owner>/<name>/pull/<number>, with anything after it (/files, a fragment) ignored; attaches when opened (integrations §3.1). The canonical URL has no suffix |
| refs | head refs/pull/<number>/head → refs/remotes/<remote>/pull/<number> (forks' pull requests have it too); base refs/heads/<baseRefName> → refs/remotes/<remote>/<baseRefName> |
| names | GitHub · pull request · pr · #<number> |
For exporting and for Attach, the app suggests pull requests from the clone's GitHub remotes, one whose head is the subject's head first. The user picks one (integrations §3, §3.1).
2. Export mapping #
A review becomes one pull request review.
| Local | GitHub |
|---|---|
change review, approve / request-changes / comment |
a review with event APPROVE / REQUEST_CHANGES / COMMENT. On the viewer's own pull request, approve and request changes post as COMMENT (shape): GitHub refuses them there. |
| audit | a COMMENT review on the pull request the user picks (shape: no verdict) |
| thread on lines in the diff | a review thread on those lines, RIGHT for head, LEFT for base |
| thread on lines outside the diff | a file thread, with the line range in the text (shape) |
| thread on a file the pull request does not change, at a commit not in the pull request, or without an anchor | part of the review body (shape, info) |
| kind and severity | a bold prefix, like "Issue · major" (info) |
| suggestion on head lines in the diff | a suggestion block; elsewhere a plain code block (shape) |
| symbol | a code span, "in orderPlan" (info) |
| evidence, when opted in | a collapsed block: command, exit code, boundary, and the last 40 lines (at most 6000 characters) of the log, in a fence |
generated_by |
a line "Generated · model" at the top of the body |
| coverage | not sent: GitHub's viewed marks are private to each user |
| entries, private threads | not sent (integrations §1) |
"The diff" is the pull request's files at planning time (REST
/repos/<owner>/<name>/pulls/<number>/files), whose patch hunks give the
lines GitHub accepts comments on. A file with no patch (binary, or too large)
takes only file threads.
Bodies are capped at 60000 characters, marker included (GitHub refuses
bodies over 65536). The cap is applied before rendering, never by cutting the
rendered text: quoted evidence logs are shortened first, then the user's
text, each at a line boundary, so that every fence and block stays closed.
The plan shows each body as it will be sent, with a shape row for each cut.
The marker is an HTML comment on the last line of every body the adapter
sends: <!-- code-browser:review <review-id> --> for the review, and
<!-- code-browser:thread <review-id>/<thread-id> --> for a thread's first
comment.
3. Sending #
An export takes these steps (integrations §6.4). Each is recorded in
progress with the node id it created.
addPullRequestReviewwith the pull request's id,commitOIDthe plan's head, and the review body: a pending review, which only the viewer can see.addPullRequestReviewThreadfor each thread placed as its own item, on the pending review:path,side,lineandstartLinefor lines, orsubjectType: FILE.submitPullRequestReviewwith the event.
| Step | Done when |
|---|---|
| 1 | a review by the viewer exists with the marker (state PENDING or later) |
| 2 | a thread of that review exists whose first comment has the marker |
| 3 | the review's state is not PENDING |
The head check of integrations §6.4 compares the plan's head with the pull
request's headRefOid. GitHub allows one pending review per user and pull
request. A pending review of the viewer that carries this review's marker is
step 1's result, found by the check of integrations §6.5. If the viewer has
any other pending review there, the export fails with retryable = 0,
saying so: it may be the user's own work, and is left alone.
Undoing (integrations §6.4) is deletePullRequestReview on the pending review
of step 1, which deletes its threads with it. A submitted review cannot be
undone.
4. Fetching #
A fetch reads (integrations §7):
| GitHub | Remote entry |
|---|---|
| the pull request | the destination's url, title, head (headRefOid), and its description: body, author and author_id from author, base_ref (baseRefName), head_ref (headRefName), created (createdAt), updated (lastEditedAt, else createdAt) |
| its state | state: merged when merged, closed for CLOSED, draft for OPEN with isDraft, else open; remote_state is state, with draft added when isDraft |
each review (reviews), the viewer's pending review included |
kind = 'review', remote_state its state, verdict approve for APPROVED, request-changes for CHANGES_REQUESTED, comment for COMMENTED, none for PENDING or DISMISSED |
each review thread (reviewThreads) |
kind = 'thread', parent_external_id its first comment's review, resolved from isResolved, resolved_by, outdated from isOutdated, the anchor from path, diffSide, startLine and line, anchor_commit from its first comment's commit (for a LEFT thread too: the head it was made on, while its lines are of the base side). Its body is its first comment's text; the first comment is not a row of its own |
| each later comment of a thread | kind = 'comment', parent_external_id the thread |
each conversation comment (comments, which belong to no review or thread) |
kind = 'comment', parent_external_id NULL |
- Pages use GraphQL cursors, 50 threads and 100 comments per page, following
every
hasNextPageincluding comments within a thread. authoris the login,author_idthe account's node id; a deleted account (authornull) isghostwith no id.edited_byiseditor's id.- Times are converted to the store's form (format §4.3):
2026-09-23T14:02:11Zbecomes2026-09-23T14:02:11.000Z. - URLs are kept only on the destination's host (integrations §5.3). The
jwtparameter is removed from private attachment links (integrations §8). - The viewer's own pending review is included, since the check of integrations §6.5 depends on it.
bodyis the pull request's markdown (body), not GitHub's rendered HTML; the app renders it as it renders every fetched body.
4.1 Checks #
Checks come from the head commit's statusCheckRollup.contexts
(commits(last: 1), whose oid MUST equal headRefOid, else the fetch
stops with fetch_error and is tried again next time), 100 per page,
following hasNextPage up to the cap of integrations §5.6.
| GitHub | Check |
|---|---|
a CheckRun |
name from its checkSuite.workflowRun.workflow.name and name joined by / when it has a workflow, else name; url its url (the run's page on GitHub, never detailsUrl, which may be another host); started and finished from startedAt and completedAt |
its status and conclusion |
QUEUED, WAITING, PENDING, REQUESTED → pending; IN_PROGRESS → running; COMPLETED with SUCCESS → passed, FAILURE, TIMED_OUT, STARTUP_FAILURE, ACTION_REQUIRED → failed, SKIPPED → skipped, CANCELLED, STALE → cancelled, NEUTRAL → neutral; remote_state is status, and conclusion after a space when there is one |
a StatusContext (commit status) |
name from context; state PENDING, EXPECTED → pending, SUCCESS → passed, FAILURE, ERROR → failed; url from targetUrl only when it passes the URL rule (it is usually another host, and is then NULL); started from createdAt |
pos is the order GitHub returns them in. A check whose type is neither is
skipped and counted in checks_omitted (integrations §5.6).
5. Credentials #
The token comes from the github.auth setting: gh, which runs
gh auth token --hostname <host>, or keychain, a token the user saved in
the OS keychain for that host. Both follow integrations §8. gh is looked up
on PATH and in the usual install directories (apps started from the Dock
get a short PATH), never in a repository.
6. Failures and rate limits #
| Response | Kind (integrations §6.6) |
|---|---|
| network error before a response, TLS failure | not sent |
| timeout or reset on a mutation, a 5xx | maybe sent |
a 403 or 429 with retry-after, or x-ratelimit-remaining: 0 (pause until x-ratelimit-reset), a GraphQL RATE_LIMITED error |
not sent; pauses the host |
a GraphQL error of type NOT_FOUND or FORBIDDEN, or a 401, 403 or 404 otherwise |
permanent |
| a GraphQL error on a mutation, such as a line outside the diff | permanent |
A GraphQL response can carry errors with a 200 status. The adapter treats
any errors on a mutation as a failure of that step, and on a query as a
failed fetch.