GitHub Tool

August 5, 2026 · View on GitHub

The github tool lets the model work with GitHub issues, pull requests, checks and labels without ever holding a GitHub credential.

Why it exists

Running gh from the shell requires a token the model can read. Inside a sandbox that defeats the point: a prompt-injected or over-eager agent can echo the token, encode it, or send it somewhere. The github tool moves the work to the host instead. The model names an operation and supplies arguments; the host runs gh and returns the result. The credential never enters the sandbox.

A second benefit falls out of the same design: because the host shapes the response, the model gets structured data instead of raw JSON, so it no longer has to write --jq expressions and no longer misses comments.

Using it

Operation names mirror gh subcommands. Parameters mirror gh long flags with the dashes removed — --repo becomes repo, --limit becomes limit. There is no --json or --jq, because responses are already shaped.

{ "op": "issue.view", "number": 1663, "comments": true }
{ "op": "issue.list", "search": "sandbox", "state": "open", "limit": 20 }
{ "op": "pr.reviews", "number": 2317, "actionable": true }
{ "op": "pr.checks", "number": 2317, "watch": true }

Working across repositories

Every operation takes an optional repo as owner/name. Omit it to use the current repository.

{ "op": "issue.view", "number": 42, "repo": "acoliver/otherproject" }

This works for public repositories, private repositories on free plans, and any account you have access to. It is not limited to GitHub Enterprise.

Operations

Reading

OperationWhat it does
issue.viewOne issue, optionally with comments
issue.listIssues, filtered by search, state, label
pr.viewOne pull request, optionally with comments
pr.listPull requests
pr.diffThe unified diff
pr.checksCI check results; see Watching checks below
pr.reviewsReview threads; see Actionable reviews below
search.issues, search.prsSearch across repositories
run.listWorkflow runs
label.listLabels

List results exclude bodies and default to 30 items (maximum 100). A long list carrying full bodies is the fastest way to blow the response budget, and it is rarely what you wanted.

Writing

OperationWhat it does
issue.create, issue.comment, issue.closeCreate, comment, close
issue.editTitle, body, labels, assignees, projects, milestone, type
pr.create, pr.comment, pr.edit, pr.readyCreate, comment, edit, mark ready
pr.resolve-threadResolve a review thread
label.createCreate a label

Write operations ask for confirmation before running, through the same mechanism as every other tool — so your normal allow rules and "always allow" choices apply.

Per-operation parameters

Every parameter an operation accepts is declared in the tool schema, so the model can see them in the function declaration rather than discovering them by trial and error. This table is the quick reference; the schema is the source of truth.

OperationRequiredOptional
issue.viewnumbercomments, repo
issue.listsearch, state, label, limit, repo
issue.createtitlebody, label, assignee, milestone, project, repo
issue.commentnumber, bodyrepo
issue.editnumbertitle, body, addLabel, removeLabel, addAssignee, removeAssignee, addProject, removeProject, milestone, type, repo
issue.closenumberreason, repo
pr.viewnumbercomments, repo
pr.liststate, limit, repo
pr.diffnumberrepo
pr.checksnumberrepo, watch
pr.reviewsnumberactionable, repo
pr.createtitlebody, base, head, draft, repo
pr.commentnumber, bodyrepo
pr.editnumbertitle, body, addLabel, removeLabel, addAssignee, milestone, repo
pr.readynumberrepo
pr.resolve-threadthreadIdrepo
search.issuesquerylimit, repo
search.prsquerylimit, repo
run.listlimit, branch, repo
label.listlimit, repo
label.createnamecolor, description, force, repo
  • number and limit are positive integers (limit 1–100).
  • state is open, closed, merged, or all. The schema lists the full set, but a per-operation value rule is enforced before the call is made: issue.list only accepts open, closed, or all (merged is rejected), while pr.list accepts the full set including merged.
  • reason is completed or "not planned".
  • label, addLabel, removeLabel, assignee, addAssignee, and removeAssignee are declared as arrays of strings in the tool schema (use a single-element array for one label or assignee). The broker still accepts a bare string for backwards compatibility, but the published contract is the array form.
  • Every operation accepts an optional repo as owner/name.

A missing required parameter, an unknown parameter, or an invalid value (such as state: "merged" on issue.list, or a non-integer number) is rejected before the call reaches GitHub, with a message that names the operation and lists what it accepts.

Actionable reviews

pr.reviews with actionable: true omits threads that are already resolved or outdated, leaving only the comments that still need action. On a pull request with a long automated review summary, this is the difference between a wall of text and a short list.

The thread id it returns is exactly what pr.resolve-thread accepts, so the two compose:

{ "op": "pr.reviews", "number": 2317, "actionable": true }
// ... address a comment ...
{ "op": "pr.resolve-thread", "threadId": "PRRT_kwDO..." }

Watching checks

pr.checks with watch: true blocks until CI finishes and then returns the result. The host runs the polling loop, so the model does not need to poll and does not fight tool timeouts.

Polling is every 10 seconds for the first 30 seconds, then every 30 seconds. Lint and format failures usually appear in the first minute, so the fast early phase surfaces the common failure quickly, while the slower steady state keeps API usage to roughly 2% of the hourly budget.

The watch ends when no check is still pending — including when checks fail. It does not wait for red checks to turn green. Press Ctrl+C to cancel; the host-side poller stops immediately.

Issue types

gh issue edit can set labels, assignees, projects and milestones, but it has no flag for issue type. issue.edit handles type as well, resolving the type name against those the repository defines:

{
  "op": "issue.edit",
  "number": 1663,
  "type": "Feature",
  "addLabel": ["security"],
}

The name is matched case-insensitively. If the repository has no matching type, the operation fails and tells you which types exist — it does not silently succeed while leaving the type unset.

Authentication

The tool uses whatever credential gh already has on the host. If gh auth status works in your terminal, the tool works.

LLxprt never reads, stores or copies that credential. It runs gh, and gh reads its own keyring entry.

Note on storing a token with /key: this is deliberately not supported. A stored token is a durable, high-value secret sitting behind the sandbox boundary, whereas host gh auth leaves nothing for the broker to hand out even if something else goes wrong. See Sandbox for the reasoning.

Errors

CodeMeaning
NOT_FOUNDNo such issue, pull request or repository
PERMISSION_DENIEDThe credential lacks access
RATE_LIMITEDGitHub rate limit hit; retry later
HOST_AUTH_REQUIREDgh is not authenticated on the host
HOST_GH_UNAVAILABLEgh is not installed on the host
INVALID_PARAMA parameter was rejected before the call was made
UNKNOWN_OPNo such operation

Sandboxed vs. not

The tool behaves identically either way. Inside a sandbox the request travels to the host over the existing authenticated credential channel; outside one it runs in the same process. The operation set and the responses are the same, so there is no behaviour that only appears in one mode.

Under Docker or Podman this is the only way to reach GitHub, because no credential exists in the container for gh to use.

Seatbelt is different. It runs on the host with your full keyring rather than isolating credentials, so gh there is already authenticated and the tool provides convenience and shaped output rather than a boundary. See Sandbox for why Docker or Podman is recommended.

Transcript display

The transcript shows a human-readable summary per operation — for example, "Commented on issue #438" with the link, or "Issue #1663 · open · Fix the thing" — rather than a raw JSON blob. Read operations render their actual content as plain text: pr.diff shows the diff (capped at 200 lines), issue.view / pr.view show the body and each comment (bodies and comments are line-capped, with a "… N more" tail), and pr.reviews shows every comment in each thread. The model still receives the full shaped JSON (including every field, comment, and label) so it has the data it needs to act; only what is displayed to you is condensed.