---
title: GitHub
description: Respond to @mentions in PR and issue comment threads.
tagline: Build bots that respond to pull request and issue comment threads. Treats issues and PRs as threads, comments as messages.
package: @chat-adapter/github
---

# GitHub



## Install

<PackageInstall package="@chat-adapter/github @chat-adapter/state-memory" />

## Quick start

<Callout type="info">
  The adapter auto-detects credentials from `GITHUB_TOKEN` (or `GITHUB_APP_ID`/`GITHUB_PRIVATE_KEY`), `GITHUB_WEBHOOK_SECRET`, and `GITHUB_BOT_USERNAME`.

  For managed credentials and webhook verification, see [Vercel Connect](#vercel-connect).
</Callout>

```typescript title="lib/bot.ts" lineNumbers
import { Chat } from "chat";
import { createGitHubAdapter } from "@chat-adapter/github";
import { createMemoryState } from "@chat-adapter/state-memory";

export const bot = new Chat({
  userName: "my-bot",
  adapters: {
    github: createGitHubAdapter(),
  },
  state: createMemoryState(),
});

bot.onNewMention(async (thread, message) => {
  await thread.post("Hello from GitHub!");
});
```

The memory state adapter keeps subscriptions and locks in process memory, which suits local development. Use [Redis](/adapters/official/redis) or [PostgreSQL](/adapters/official/postgres) in production.

Then create the webhook route:

```typescript title="app/api/webhooks/github/route.ts" lineNumbers
import { after } from "next/server";
import { bot } from "@/lib/bot";

export async function POST(request: Request): Promise<Response> {
  return bot.webhooks.github(request, {
    waitUntil: (task) => after(() => task),
  });
}
```

## Authentication

### Vercel Connect

Use [Vercel Connect](https://vercel.com/docs/connect) to source installation access tokens at runtime instead of storing a GitHub App private key. The `connectGitHubAdapter()` helper from [`@vercel/connect/chat`](https://www.npmjs.com/package/@vercel/connect) wires an `installationToken` resolver, which skips the App JWT exchange, and a `webhookVerifier` for Connect trigger-forwarded webhooks:

```typescript
import { createGitHubAdapter } from "@chat-adapter/github";
import { connectGitHubAdapter } from "@vercel/connect/chat";

createGitHubAdapter({
  ...connectGitHubAdapter("github/acme-github"),
  userName: "my-bot[bot]",
});
```

`installationToken` accepts a `string` or `() => string | Promise<string>` resolver invoked per API call. When `webhookVerifier` is set it takes precedence over `webhookSecret` and `GITHUB_WEBHOOK_SECRET`.

<Callout type="warn">
  In Connect mode the adapter holds only an installation token, so it can't auto-detect its own bot user ID: the `/app` lookup needs the App's JWT. Without that ID, the adapter can't recognize its own comments and replies to itself in a loop. It learns the ID from the first comment it posts, but only in memory, which isn't enough on serverless where each webhook can hit a fresh instance. Pass `botUserId`, the numeric ID of your `your-app[bot]` user, so every instance knows it up front. You can look it up with `curl -s 'https://api.github.com/users/your-app%5Bbot%5D'`.

  ```typescript
  createGitHubAdapter({
    ...connectGitHubAdapter("github/acme-github"),
    botUserId: 12345678,
  });
  ```

  Or set the `GITHUB_BOT_USER_ID` environment variable, which the adapter auto-detects.
</Callout>

### GitHub App

We recommend a GitHub App. It has better rate limits and security than a personal access token and supports multiple installations.

#### Create the app

1. Go to [Settings then Developer settings then GitHub Apps then New GitHub App](https://github.com/settings/apps/new).
2. Set **Webhook URL** to `https://your-domain.com/api/webhooks/github`.
3. Set a **Webhook secret**.
4. Under permissions, set Issues and Pull requests to Read & write, and Metadata to Read-only.
5. Subscribe to events: Issue comment, Pull request review comment.
6. Click **Create GitHub App** and generate a private key.

#### Install the app

1. From your app's settings click **Install App** and choose repositories.
2. Note the **Installation ID** in the URL: `https://github.com/settings/installations/12345678`.

#### Single-tenant configuration

```typescript
createGitHubAdapter({
  appId: process.env.GITHUB_APP_ID!,
  privateKey: process.env.GITHUB_PRIVATE_KEY!,
  installationId: parseInt(process.env.GITHUB_INSTALLATION_ID!, 10),
});
```

#### Multi-tenant configuration

Omit `installationId`:

```typescript
createGitHubAdapter({
  appId: process.env.GITHUB_APP_ID!,
  privateKey: process.env.GITHUB_PRIVATE_KEY!,
});
```

The adapter extracts installation IDs from incoming webhooks and caches API clients per installation.

### Personal access token

A personal access token suits personal projects, testing, and single-repo bots.

1. Go to [Settings then Developer settings then Personal access tokens](https://github.com/settings/tokens).
2. Create a token with `repo` scope.
3. Set `GITHUB_TOKEN`.

```typescript
createGitHubAdapter({
  token: process.env.GITHUB_TOKEN!,
});
```

## Configuration

<TypeTable
  type={{
  token: {
    type: "string",
    description:
      "Personal Access Token. Auto-detected from `GITHUB_TOKEN`.",
  },
  appId: {
    type: "string | number",
    description: "GitHub App ID. Auto-detected from `GITHUB_APP_ID`.",
  },
  privateKey: {
    type: "string",
    description:
      "GitHub App private key (PEM). Auto-detected from `GITHUB_PRIVATE_KEY`.",
  },
  installationId: {
    type: "number",
    description:
      "Installation ID. Omit for multi-tenant setups so the adapter resolves it from each webhook.",
  },
  installationToken: {
    type: "string | (() => string | Promise<string>)",
    description:
      "Vercel Connect mode: installation access token (or a resolver invoked per API call). Skips the GitHub App JWT exchange.",
  },
  webhookSecret: {
    type: "string",
    description:
      "Webhook secret. Auto-detected from `GITHUB_WEBHOOK_SECRET`.",
  },
  webhookVerifier: {
    type: "(request, body) => unknown | Promise<unknown>",
    description:
      "Custom verifier used in place of `webhookSecret` (e.g. Vercel Connect OIDC). Takes precedence over the secret; required in Connect mode.",
  },
  userName: {
    type: "string",
    default: '"github-bot"',
    description:
      "Bot username for @mention detection. Auto-detected from `GITHUB_BOT_USERNAME`.",
  },
  botUserId: {
    type: "number",
    description:
      "Numeric ID of the bot user, used to ignore the bot's own comments. Auto-detected from `GITHUB_BOT_USER_ID`. Set it in Vercel Connect mode, where the adapter can't look it up. See [Vercel Connect](#vercel-connect).",
  },
  apiUrl: {
    type: "string",
    description:
      "Override the GitHub API base URL (e.g. for GitHub Enterprise Server).",
  },
}}
/>

One of `token`, `appId`+`privateKey`, or `installationToken` (Vercel Connect) is
required, plus either `webhookSecret` or a `webhookVerifier`.

### Environment variables

| Variable                 | Required                              | Description                                                                   |
| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------------- |
| `GITHUB_TOKEN`           | One auth mode required                | Personal access token.                                                        |
| `GITHUB_APP_ID`          | One auth mode required                | GitHub App ID. Use with `GITHUB_PRIVATE_KEY`.                                 |
| `GITHUB_PRIVATE_KEY`     | One auth mode required                | GitHub App private key (PEM). Use with `GITHUB_APP_ID`.                       |
| `GITHUB_INSTALLATION_ID` | No                                    | Installation ID for a single-tenant GitHub App. Omit for multi-tenant setups. |
| `GITHUB_WEBHOOK_SECRET`  | Yes, unless using a `webhookVerifier` | Webhook secret.                                                               |
| `GITHUB_BOT_USERNAME`    | No                                    | Bot username for @mention detection. Defaults to `github-bot`.                |
| `GITHUB_BOT_USER_ID`     | Recommended with Vercel Connect       | Numeric ID of the bot user, so the adapter can recognize its own comments.    |

## Webhooks

For repository or organization webhooks:

1. Open **Settings** then **Webhooks** then **Add webhook**.
2. Set the payload URL to `https://your-domain.com/api/webhooks/github`.
3. Set **Content type** to `application/json`. The default `application/x-www-form-urlencoded` does not work.
4. Set **Secret** to match `webhookSecret`.
5. Select events: Issue comments, Pull request review comments.

You configure GitHub App webhooks when you create the app. Make sure to select `application/json` there too.

## Reactions

| SDK emoji     | GitHub reaction |
| ------------- | --------------- |
| `thumbs_up`   | +1              |
| `thumbs_down` | -1              |
| `laugh`       | laugh           |
| `confused`    | confused        |
| `heart`       | heart           |
| `hooray`      | hooray          |
| `rocket`      | rocket          |
| `eyes`        | eyes            |

## Installation lookup

Resolve the GitHub App installation ID associated with a `Thread` or `Message`:

```typescript
bot.onNewMention(async (thread, message) => {
  const installationIdFromThread = await github.getInstallationId(thread);
  const installationIdFromMessage = await github.getInstallationId(
    message.threadId
  );
});
```

The result depends on the auth mode:

* With a personal access token, it returns `undefined`.
* With a single-tenant GitHub App, it returns the configured installation ID.
* With a multi-tenant GitHub App, it succeeds only after the adapter has received a webhook for that repository and cached the mapping. Use a persistent state adapter so the mapping survives restarts.

## Direct API client

Access the underlying [Octokit](https://github.com/octokit/octokit.js) instance through `.octokit`:

```typescript
const github = bot.getAdapter("github").octokit;
const { data: pulls } = await github.rest.pulls.list({
  owner: "vercel",
  repo: "chat",
  state: "open",
});
```

Personal access token and single-tenant App modes return the same client anywhere. Multi-tenant mode requires webhook handler context, and calling `.octokit` outside a handler throws.

The previous `.client` getter still works as a deprecated alias for `.octokit`.

## Thread IDs

Issues and pull requests map to threads, and comments map to messages:

| Type            | Context              | Thread ID format                                  |
| --------------- | -------------------- | ------------------------------------------------- |
| PR-level        | PR Conversation tab  | `github:{owner}/{repo}:{prNumber}`                |
| Review comments | PR Files Changed tab | `github:{owner}/{repo}:{prNumber}:rc:{commentId}` |
| Issue comments  | Issue thread         | `github:{owner}/{repo}:issue:{issueNumber}`       |

## Feature support

<FeatureSupport />

## Resources

* [Ship a GitHub code review bot with Hono and Redis](https://vercel.com/kb/guide/ship-a-github-code-review-bot-with-hono-and-redis?utm_source=chat-sdk_site\&utm_medium=docs\&utm_campaign=adapter-github\&utm_content=ship-a-github-code-review-bot-with-hono-and-redis): build a GitHub bot that reviews pull requests on demand. When a user @mentions the bot on a PR, Chat SDK picks up the mention, spins up a Vercel Sandbox with the repo cloned, and uses AI SDK to analyze the diff.

See all guides and templates on the [resources](/resources?utm_source=chat-sdk_site\&utm_medium=docs\&utm_campaign=adapter-github\&utm_content=resources) page.
