---
title: "Octokit and gh CLI"
description: "When to call GitHub with Octokit or the gh CLI instead of GitHub tools, how they differ for an AI agent, and how to combine them."
canonical_url: "https://github-tools.com/compare/octokit-and-gh-cli"
---
# Octokit and gh CLI

> When to call GitHub with Octokit or the gh CLI instead of GitHub tools, how they differ for an AI agent, and how to combine them.

Octokit and the gh CLI call the GitHub API on behalf of code or a person, while GitHub tools turns those API calls into typed tools that a model chooses and fills in. GitHub tools uses Octokit underneath, and adds what an agent needs on top: input schemas, approval before writes, results trimmed for a model, and errors the model can act on.

## What each one is

**Octokit** is GitHub's JavaScript SDK. Your code picks each endpoint and its parameters, such as `octokit.rest.issues.addLabels(...)`, and gets GitHub's full response back. There is no model in the loop unless you put one there.

**The gh CLI** is GitHub's command-line tool. An agent uses it through a shell tool: the model writes a command such as `gh issue list --label bug --json number,title`, your code runs it, and the model reads the text output.

**GitHub tools** gives the model 97 named tools, such as `listIssues` and `addLabels`, each with a Zod input schema and a shaped result. The model picks the tool and fills its input, and your code decides which calls wait for a person.

<three-paths>



</three-paths>

## Comparison

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      GitHub tools
    </th>
    
    <th>
      Octokit
    </th>
    
    <th>
      gh CLI through a shell tool
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Who picks the call
    </td>
    
    <td>
      The model, from tool descriptions
    </td>
    
    <td>
      Your code
    </td>
    
    <td>
      The model, as a command string
    </td>
  </tr>
  
  <tr>
    <td>
      Input checking
    </td>
    
    <td>
      A Zod schema per tool, checked before the call
    </td>
    
    <td>
      TypeScript types at build time
    </td>
    
    <td>
      The CLI's flag parsing at run time
    </td>
  </tr>
  
  <tr>
    <td>
      Approval
    </td>
    
    <td>
      Per write tool, on by default. See <a href="https://github-tools.com/approval">
        Approval
      </a>
    </td>
    
    <td>
      None: your code runs what it calls
    </td>
    
    <td>
      Whatever your shell tool asks, per command string
    </td>
  </tr>
  
  <tr>
    <td>
      Scoping
    </td>
    
    <td>
      <a href="https://github-tools.com/presets">
        Presets
      </a>
      
       and the token's permissions
    </td>
    
    <td>
      The token's permissions
    </td>
    
    <td>
      The token's permissions, plus anything else the shell can run
    </td>
  </tr>
  
  <tr>
    <td>
      Output size
    </td>
    
    <td>
      Shaped results with paging, bodies cut to about 500 characters
    </td>
    
    <td>
      GitHub's full response
    </td>
    
    <td>
      Text, or the fields the model lists in <code>
        --json
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Errors
    </td>
    
    <td>
      A code, cause and fix the model can read. See <a href="https://github-tools.com/errors">
        Errors
      </a>
    </td>
    
    <td>
      Exceptions with GitHub's status and message
    </td>
    
    <td>
      Text on stderr and an exit code
    </td>
  </tr>
  
  <tr>
    <td>
      Auth
    </td>
    
    <td>
      A token, a function per call, or <a href="https://github-tools.com/auth#vercel-connect">
        Vercel Connect
      </a>
    </td>
    
    <td>
      A token or a GitHub App
    </td>
    
    <td>
      <code>
        gh auth login
      </code>
      
       or <code>
        GH_TOKEN
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Runtime
    </td>
    
    <td>
      Any Node.js process
    </td>
    
    <td>
      Any JavaScript runtime
    </td>
    
    <td>
      A shell with <code>
        gh
      </code>
      
       installed, ideally a sandbox
    </td>
  </tr>
</tbody>
</table>

## When to pick each

Pick **GitHub tools** when a model decides what to do on GitHub: an agent that reviews, triages or reports. The schemas keep the model's input valid, approval keeps writes in front of a person, and the trimmed results keep context for the task.

Pick **Octokit** when your code already knows the call to make. A webhook handler that checks a commenter's permission, a script that copies labels between repositories, or a step before or after the agent doesn't need a model to choose the endpoint.

Pick **the gh CLI** when the agent already works in a shell, such as a coding agent in a sandbox, and needs a GitHub operation that no tool covers. `gh api` reaches any REST or GraphQL endpoint. Approval is then per command, so a person reads a command string rather than a tool name and typed input.

## Use Octokit next to GitHub tools

`createOctokit` from `@github-tools/sdk` returns the same Octokit client the tools use: it sends the GitHub API version the tools are built against and turns failures into the same [error codes](https://github-tools.com/errors). `resolveGithubToken` resolves the token the same way as the tools, from `GITHUB_TOKEN`, a string, a function or a Vercel Connect token.

Use them to write a tool for an endpoint GitHub tools doesn't cover, and give the model both:

```ts [contributors.ts]
import { createGithubTools, createOctokit, resolveGithubToken } from '@github-tools/sdk'
import { generateText, stepCountIs, tool } from 'ai'
import { z } from 'zod'

const listContributors = tool({
  description: 'List the top contributors to a repository, by number of commits',
  inputSchema: z.object({
    owner: z.string().describe('Repository owner'),
    repo: z.string().describe('Repository name'),
  }),
  execute: async ({ owner, repo }) => {
    const octokit = createOctokit(await resolveGithubToken())
    const { data } = await octokit.rest.repos.listContributors({ owner, repo, per_page: 10 })
    return data.map(user => ({ login: user.login, contributions: user.contributions }))
  },
})

const { text } = await generateText({
  model: 'anthropic/claude-opus-5.5',
  tools: { ...createGithubTools({ preset: 'repo-explorer' }), listContributors },
  stopWhen: stepCountIs(5),
  prompt: 'Who are the top contributors to vercel/ai, and which open pull requests are theirs?',
})
```

The tool returns two fields per contributor instead of GitHub's full user objects, the same way the built-in tools shape their results. If it writes to GitHub, set `needsApproval: true` on it so it asks like the built-in writes.

The [pull request review guide](https://github-tools.com/guides/review-pull-requests#_2-scope-the-token-to-one-repository) uses `createOctokit` the other way: plain code that checks a commenter's permission before the agent runs.

## FAQ

<accordion>
<accordion-item label="Does GitHub tools use Octokit?">

Yes. Every tool calls GitHub through the `octokit` package: the REST API for most tools, and GraphQL for a few, such as discussions and review threads. Each returns a shaped result instead of the raw response.

</accordion-item>

<accordion-item label="Can I get the raw GitHub response from a tool?">

No. Tools return the fields a model needs. Call the endpoint with `createOctokit` when your code needs the full response.

</accordion-item>

<accordion-item label="Is it safe to give an agent the gh CLI?">

It depends on the shell. A shell tool can run any command, not only `gh`, so run it in a sandbox, keep the token narrow, and approve commands before they run. GitHub tools limits the model to named operations without a shell.

</accordion-item>

<accordion-item label="Can a tool I write with Octokit fail like the built-in tools?">

Yes. Calls through `createOctokit` already fail with the same codes, and `githubToolsErrors` builds the same errors for your own checks. See [Errors](https://github-tools.com/errors#fail-the-same-way-in-your-own-tools).

</accordion-item>
</accordion>


## Sitemap

See the full [sitemap](https://github-tools.com/sitemap.md) for all pages.
