---
title: "Auth"
description: "Give the tools a GitHub token with only the permissions the agent needs."
canonical_url: "https://github-tools.com/auth"
---
# Auth

> Give the tools a GitHub token with only the permissions the agent needs.

## Overview

GitHub tools authenticates every call with a GitHub token, and the token's permissions decide what the call can do. A token that can't push can't change your code, whatever the model decides, so give the agent only the permissions its job needs.

The token can come from the environment, from your own code on every call, or from Vercel Connect, which mints short-lived tokens on demand so there's no secret to store.

## Configure

Set `token` where you create the tools or the agent. Without it, the SDK reads `GITHUB_TOKEN`:

```ts [tools.ts]
import { createGithubTools } from '@github-tools/sdk'

const tools = createGithubTools({
  preset: 'issue-triage',
  token: async (call) => getInstallationToken(call?.owner),
})
```

<table>
<thead>
  <tr>
    <th>
      <code>
        token
      </code>
    </th>
    
    <th>
      When it's read
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      omitted
    </td>
    
    <td>
      <code>
        GITHUB_TOKEN
      </code>
      
      , when the tools are created
    </td>
  </tr>
  
  <tr>
    <td>
      a string
    </td>
    
    <td>
      Once, when the tools are created
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        (call?) => Promise<string>
      </code>
    </td>
    
    <td>
      On every tool call
    </td>
  </tr>
</tbody>
</table>

A function runs on every tool call, so a short-lived token stays fresh. It receives the call: `toolName`, `input` after [context](https://github-tools.com/context) defaults, and `owner` and `repo` when the tool targets a repository.

## Use a fine-grained token

For local work and single-tenant agents, create a [fine-grained personal access token](https://github.com/settings/personal-access-tokens/new) limited to the agent's repositories. Start with read access, and add write permissions only for the writes you plan to allow.

## Permissions per preset

The table shows what each [preset](https://github-tools.com/presets) needs to run all of its tools. Every token also needs Metadata (read). Read access is enough when you only allow the preset's reads.

<scope-matrix>



</scope-matrix>

<table>
<thead>
  <tr>
    <th>
      Preset
    </th>
    
    <th>
      Contents
    </th>
    
    <th>
      Pull requests
    </th>
    
    <th>
      Issues
    </th>
    
    <th>
      Discussions
    </th>
    
    <th>
      Actions
    </th>
    
    <th>
      Checks, statuses
    </th>
    
    <th>
      Other
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        repo-explorer
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      Gists, for the gist tools
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        code-review
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      write
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      read, Commit statuses (write)
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        issue-triage
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      write
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ci-ops
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      write
    </td>
    
    <td>
      read, Commit statuses (write)
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        security-audit
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      write
    </td>
    
    <td>
      
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      Code scanning, Dependabot and secret scanning alerts (read)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        release-manager
      </code>
    </td>
    
    <td>
      write
    </td>
    
    <td>
      read
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      write
    </td>
    
    <td>
      read
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        discussion-moderator
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        notification-inbox
      </code>
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      read
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      Notifications
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pr-author
      </code>
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
    
    <td>
      
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maintainer
      </code>
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      write
    </td>
    
    <td>
      read, Commit statuses (write)
    </td>
    
    <td>
      Administration (write), the three alert permissions (read), Gists, Notifications
    </td>
  </tr>
</tbody>
</table>

Releases fall under Contents and reactions under Issues, so neither needs a permission of its own.

`createCommitStatus` is the only tool that needs Commit statuses (write). With a personal access token, only that tool fails without it. With [Vercel Connect](#leave-a-tool-out-of-the-token), the preset's token asks for `statuses:write` on every call, so a GitHub App without that permission can make every tool in the preset fail. `getCollaboratorPermission` needs only Metadata (read). `updatePullRequestBranch` needs Pull requests (write), and a GitHub App also needs Contents (write) on the pull request's head repository.

## Vercel Connect

On Vercel, [Vercel Connect](https://vercel.com/docs/connect) replaces the stored token. A GitHub connector, a GitHub App that Vercel manages, is attached to your project, and your code asks it for a short-lived token scoped to the preset. There's no secret to store or rotate.

<connect-token-flow>



</connect-token-flow>

### Create a GitHub connector

1. Create a connector of type `github` from the [Vercel dashboard](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fconnect%2Fcreate%3Ftype%3Dgithub), or with `vercel connect`.
2. Install its GitHub App on the organization or account the agent works on, and pick the repositories.
3. Link the connector to the project that runs the agent. Deployments authenticate with their OIDC token. Locally, run `vercel link`, then `vercel env pull`.
4. Install `@vercel/connect`, an optional peer dependency used only by `@github-tools/sdk/connect`:

<code-group>

```bash [pnpm]
pnpm add @vercel/connect
```

```bash [npm]
npm install @vercel/connect
```

```bash [yarn]
yarn add @vercel/connect
```

```bash [bun]
bun add @vercel/connect
```

</code-group>

### Use the connector

`connectGithubTools` takes the connector name and the usual [tool options](https://github-tools.com/tools#options), and derives the token's scopes from `preset`:

```ts [connect.ts]
import { connectGithubTools } from '@github-tools/sdk/connect'
import { generateText } from 'ai'

const { text } = await generateText({
  model: 'anthropic/claude-opus-5.5',
  tools: connectGithubTools('github/my-connector', { preset: 'code-review' }),
  prompt: 'Summarize the open pull requests on my-org/my-repo.',
})
```

The token is minted inside each tool call, and Connect caches it per connector and parameters.

For `createGithubTools` or a [single tool](https://github-tools.com/tools#pick-single-tools), `connectGithubToken` returns the token function. Pass it the same `preset`, since it derives its scopes on its own:

```ts [connect-token.ts]
import { createGithubTools } from '@github-tools/sdk'
import { connectGithubToken } from '@github-tools/sdk/connect'

const tools = createGithubTools({
  preset: 'ci-ops',
  token: connectGithubToken('github/my-connector', { preset: 'ci-ops' }),
})
```

To scope the token to a hand-picked tool set instead, pass `include` and `exclude`. `connectGithubScopesForPreset` and its siblings return the same scope lists, see the [API reference](https://github-tools.com/reference/api).

### Leave a tool out of the token

A preset's token asks for the scopes of every tool in it, so the connector's GitHub App needs each of those permissions. `code-review`, `ci-ops` and `maintainer` include `createCommitStatus`, which needs Commit statuses (read and write). Add that permission to the App, or leave the tool out of the token.

`connectGithubTools` always asks for the whole preset's scopes. Build the token with `connectGithubToken` and `exclude`, and drop the tool from the tool set:

```ts [connect-without-commit-status.ts]
import { createGithubTools } from '@github-tools/sdk'
import { connectGithubToken } from '@github-tools/sdk/connect'

const token = connectGithubToken('github/my-connector', { preset: 'code-review', exclude: ['createCommitStatus'] })
const { createCommitStatus, ...tools } = createGithubTools({ preset: 'code-review', token })
```

On the [eve extension](https://github-tools.com/integrations/eve#use-vercel-connect), `exclude: ['createCommitStatus']` removes the tool and its scope together.

### Use one app on several accounts

When the App is installed on several accounts, each call that targets `owner/repo` gets a token from the installation that owns that repository. Calls without a repository target, such as search, use the connector's default installation.

To pin one installation or narrow the token, pass Connect parameters as `connect`:

```ts [connect-scoped.ts]
const tools = connectGithubTools('github/my-connector', {
  preset: 'issue-triage',
  connect: {
    installationId: 'inst_abc',
    repositories: ['my-org/my-repo'],
  },
})
```

`installationId`, `authorizationDetails` or `repositories` turn off the per-repository targeting, and `scopes` replaces the derived scopes. `connect` can also be a function of the call, `(call) => params`.

### Per-user tokens

By default every caller acts as the App, with the project's access. When each user connects their own GitHub account, pass a user subject so calls act as that user:

```ts [connect-user.ts]
const tools = connectGithubTools('github/my-connector', {
  preset: 'issue-triage',
  connect: { subject: { type: 'user', id: currentUserId } },
})
```

A user who hasn't connected GitHub gets [`CONNECT_USER_NOT_CONNECTED`](https://github-tools.com/errors) instead of falling back to the App.

### Call getToken yourself

For full control over the Connect parameters, call `getToken` from `@vercel/connect` inside a token function:

```ts [get-token.ts]
import { createGithubTools } from '@github-tools/sdk'
import { getToken } from '@vercel/connect'

const tools = createGithubTools({
  preset: 'code-review',
  token: () => getToken('github/my-connector', {
    subject: { type: 'app' },
    scopes: ['contents:read', 'pull_requests:read'],
  }),
})
```

### Expired OIDC tokens

When `VERCEL_OIDC_TOKEN` is set, as after `vercel env pull`, the SDK passes it to Connect directly. This also works in sandboxes without a `.vercel` directory. That token isn't refreshed: once it expires, calls fail with [`OIDC_TOKEN_EXPIRED`](https://github-tools.com/errors) before any request. Run `vercel env pull` again. Deployments get a fresh token on their own.

## FAQ

<accordion>
<accordion-item label="What happens without a token?">

`createGithubTools()` throws [`TOKEN_REQUIRED`](https://github-tools.com/errors). A token function that returns an empty string fails the same way, when the tool is called.

</accordion-item>

<accordion-item label="Does a durable workflow fetch a new token when it retries a step?">

No, a [durable workflow](https://github-tools.com/integrations/workflow) saves the token with the step's arguments, so a retried step reuses it.

</accordion-item>

<accordion-item label="Why does merging need Contents (write)?">

Merging needs Contents (write) because it writes to the base branch.

</accordion-item>

<accordion-item label="Which tools need Administration (write)?">

`forkRepository` and `createRepository` need Administration (write). Only the `maintainer` preset includes them.

</accordion-item>

<accordion-item label="Why do the gist and notification tools fail with an installation token?">

Gists and notifications belong to a user, so their tools need a personal access token with the **Gists** or **Notifications** account permission. With an installation token they fail with [`FORBIDDEN`](https://github-tools.com/errors), and the rest of the preset keeps working.

</accordion-item>

<accordion-item label="What if the token lacks the alert permissions?">

The code scanning, Dependabot and secret scanning tools fail, and the rest of the preset keeps working. Code scanning also has to be set up on the repository.

</accordion-item>

<accordion-item label="What scopes does a Connect token get without a preset?">

Without `preset` or `include`, the token gets the scopes of all 97 tools, Administration (write) included.

</accordion-item>

<accordion-item label="What if the App isn't installed on the target account?">

The call fails with [`CONNECT_INSTALLATION_REQUIRED`](https://github-tools.com/errors), and the message names the account.

</accordion-item>

<accordion-item label="Can I use a different connector per environment or tenant?">

Yes, the connector name can be a function, which runs on every token request.

</accordion-item>

<accordion-item label="Why call getToken inside a function?">

A top-level `await getToken(...)` would run at import or build time, where there's no OIDC token.

</accordion-item>

<accordion-item label="How does this work on eve?">

On eve, pass `connector` to the [eve extension](https://github-tools.com/integrations/eve) instead. For per-user tokens, eve uses `actAs: 'user'` or `'caller'`.

</accordion-item>
</accordion>


## Sitemap

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