Installation
npm install @chat-adapter/github
Environment Variables
Create a GitHub App or use a Personal Access Token. GitHub Apps are recommended for team/organization bots.
| Variable | Required | Description |
|---|
GITHUB_TOKEN | PAT mode | Personal Access Token with repo scope |
GITHUB_APP_ID | App mode | GitHub App ID |
GITHUB_PRIVATE_KEY | App mode | Private key (PEM format) |
GITHUB_INSTALLATION_ID | Single-tenant app | Installation ID for single org |
GITHUB_WEBHOOK_SECRET | Yes | Webhook secret for signature verification |
GITHUB_BOT_USERNAME | Optional | Bot username (e.g., my-bot[bot]) |
Configuration Options
type GitHubAdapterConfig =
| GitHubAdapterPATConfig
| GitHubAdapterAppConfig
| GitHubAdapterMultiTenantAppConfig;
// Personal Access Token
interface GitHubAdapterPATConfig {
token: string;
webhookSecret: string;
userName: string;
botUserId?: number;
logger: Logger;
}
// Single-tenant GitHub App
interface GitHubAdapterAppConfig {
appId: string;
privateKey: string;
installationId: number;
webhookSecret: string;
userName: string;
botUserId?: number;
logger: Logger;
}
// Multi-tenant GitHub App
interface GitHubAdapterMultiTenantAppConfig {
appId: string;
privateKey: string;
// No installationId - resolved per-repository from webhooks
webhookSecret: string;
userName: string;
botUserId?: number;
logger: Logger;
}
Setup
Personal Access Token
Single-Tenant App
Multi-Tenant App
For personal projects or testing:import { Chat } from 'chat';
import { createGitHubAdapter } from '@chat-adapter/github';
import { MemoryState } from '@chat-adapter/state-memory';
const chat = new Chat({
userName: 'my-bot',
adapters: {
github: createGitHubAdapter({
token: process.env.GITHUB_TOKEN!,
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
userName: 'my-bot',
}),
},
state: new MemoryState(),
});
await chat.initialize();
PAT mode uses your personal account. GitHub Apps are recommended for organization bots.
For internal bots serving one organization:import { Chat } from 'chat';
import { createGitHubAdapter } from '@chat-adapter/github';
import { MemoryState } from '@chat-adapter/state-memory';
const chat = new Chat({
userName: 'my-bot[bot]',
adapters: {
github: createGitHubAdapter({
appId: process.env.GITHUB_APP_ID!,
privateKey: process.env.GITHUB_PRIVATE_KEY!,
installationId: parseInt(process.env.GITHUB_INSTALLATION_ID!),
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
userName: 'my-bot[bot]',
}),
},
state: new MemoryState(),
});
await chat.initialize();
For public apps installable by any organization:import { Chat } from 'chat';
import { createGitHubAdapter } from '@chat-adapter/github';
import { RedisState } from '@chat-adapter/state-redis';
const chat = new Chat({
userName: 'my-bot[bot]',
adapters: {
github: createGitHubAdapter({
appId: process.env.GITHUB_APP_ID!,
privateKey: process.env.GITHUB_PRIVATE_KEY!,
// No installationId - auto-detected from webhooks
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
userName: 'my-bot[bot]',
}),
},
state: new RedisState({ url: process.env.REDIS_URL }),
});
await chat.initialize();
Multi-tenant mode requires a persistent StateAdapter to cache installation IDs.
Webhook Handler
app.post('/webhooks/github', async (req, res) => {
const response = await github.handleWebhook(req, {
waitUntil: (promise) => {/* handle async work */},
});
res.status(response.status).send(await response.text());
});
Configure webhook in your GitHub repository or app settings:
- Payload URL:
https://your-app.com/webhooks/github
- Content type:
application/json
- Secret: Your webhook secret
- Events:
Issue comments, Pull request review comments
Features
Supported Events
issue_comment (created) - Comments on PR Conversation tab
pull_request_review_comment (created) - Line-specific comments on Files Changed tab
Thread Types
GitHub adapter supports two thread types:
1. PR-level threads (Conversation tab)
github:{owner}/{repo}:{prNumber}
All comments on the PR’s main conversation.
2. Review comment threads (Files Changed tab)
github:{owner}/{repo}:{prNumber}:rc:{reviewCommentId}
Line-specific discussion threads on code changes.
Post to PR conversation:
// Thread ID: github:vercel/next.js:12345
await thread.post('LGTM! Approving this PR.');
Reply to a review comment thread:
// Thread ID: github:vercel/next.js:12345:rc:98765
await thread.post('Fixed in the latest commit.');
Markdown Support
GitHub supports full GitHub Flavored Markdown:
await thread.post(`
## Code Review
- [✓] Tests pass
- [✓] No lint errors
- [ ] Documentation updated
```typescript
const foo = 'bar';
`);
### Reactions
Add/remove reactions to comments:
```typescript
// GitHub reaction types: +1, -1, laugh, confused, heart, hooray, rocket, eyes
await thread.addReaction(messageId, { name: 'thumbs_up' });
await thread.removeReaction(messageId, { name: 'thumbs_up' });
Cards
Cards are converted to GitHub Flavored Markdown tables:
import { Card, Section, Button } from 'chat/cards';
await thread.post(
<Card title="Build Status">
<Section text="Build #42 completed successfully" />
<Button actionId="deploy">Deploy</Button>
</Card>
);
// Renders as formatted markdown table
GitHub doesn’t support interactive components. Buttons are rendered as markdown links.
Thread IDs
GitHub thread IDs encode repository and PR information:
github:{owner}/{repo}:{prNumber}[:rc:{commentId}]
Examples:
- PR conversation:
github:vercel/next.js:12345
- Review thread:
github:vercel/next.js:12345:rc:98765
Message History
Fetch comments from a thread:
const { messages } = await thread.fetchMessages({
limit: 100,
direction: 'backward',
});
Listing Threads
List all open PRs (threads) in a repository:
const { threads, nextCursor } = await github.listThreads(
'github:vercel/next.js',
{ limit: 30 }
);
for (const thread of threads) {
console.log(thread.rootMessage.text); // PR title
}
- Comment length: 65,536 characters
- File size: 25 MB per file in PR
- Rate limits: 5,000 requests/hour (authenticated), 60 requests/hour (unauthenticated)
See GitHub rate limits for details.
Code Examples
chat.onNewMessage(async (event) => {
if (event.message.text.includes('/deploy')) {
await event.thread.post('Deploying to staging...');
}
});
Troubleshooting
- Verify
GITHUB_WEBHOOK_SECRET matches the secret in GitHub settings
- Ensure webhook Content-Type is
application/json
- Check that request body is raw (not parsed)
Bot doesn't respond to comments
Multi-tenant mode not working
- Use a persistent StateAdapter (Redis, not Memory)
- Ensure webhooks include
installation field
- Check that installation IDs are being cached (debug logs)
Can't post to review threads
- Use the correct thread ID format:
github:owner/repo:123:rc:456
- Review comment ID is the ROOT comment, not the reply
- Ensure bot has write access to the repository
Required Permissions
GitHub App Permissions:
- Pull requests: Read & write (post comments, read PRs)
- Issues: Read & write (for PR conversations)
- Metadata: Read (repository metadata)
Personal Access Token Scopes:
repo - Full repository access
Creating a GitHub App
Configure permissions
- Pull requests: Read & write
- Issues: Read & write
- Metadata: Read
Subscribe to events
- Issue comments
- Pull request review comments
Generate private key
Generate and download the private key PEM file.
Install app
Install the app on your account or organization to get the installation ID.
Next Steps
Message Handling
Process comments and build review flows
GitHub Apps
Learn about GitHub Apps
Code Review Bots
Build automated code review assistants
State Management
Persist data across PR comments