---
title: "GitHub"
description: "Reach your agent from GitHub App webhooks, with comment invocation, PR diff context, sandbox checkout, and Vercel Connect credentials."
type: integration
---

The GitHub channel lets the agent work directly on a repository. Add its invocation token, such as `@my-agent`, to a new issue, PR, or review comment and the agent answers in that thread, with the PR diff already in context and the repo checked out into the sandbox. The token is an eve convention: GitHub may not autocomplete it or render it as a linked mention. The channel takes GitHub App webhooks at `/eve/v1/github`, checks the signature, derives auth from whoever triggered the event, and replies on the native surface. Credentials can run through [Vercel Connect](../guides/auth-and-route-protection), which manages the GitHub App, the installation token, and inbound webhook verification, so there's no app private key or webhook secret for you to hold. See [Channels](./overview) for the contract this builds on.

## Guided Connect setup

Run the registry setup from the agent directory:

```bash
eve add channel/github
```

The flow checks that the Vercel CLI is authenticated, creates or links a Vercel project when needed, provisions an app-scoped GitHub Connect client, and replaces its default trigger with `/eve/v1/github`. It then installs `@vercel/connect` and writes `agent/channels/github.ts` with the connector UID.

Vercel Connect creates the GitHub App, receives and verifies its webhooks, and forwards them to the deployed agent. After deploying, open the GitHub App in the Connect dashboard and install it in the organization or account where you want to use it. Add the generated invocation token (for example, `@my-agent`) to a new issue, pull request, or review comment to start a conversation. GitHub may not autocomplete the token or render it as a linked mention.

The generated channel uses Connect-managed credentials:

```ts title="agent/channels/github.ts"
import { connectGitHubCredentials } from "@vercel/connect/eve";
import { githubChannel } from "eve/channels/github";

export default githubChannel({
  botName: "my-agent",
  credentials: connectGitHubCredentials("github/my-agent"),
});
```

`connectGitHubCredentials` returns `{ installationToken, webhookVerifier }`: eve uses the Connect-managed installation token directly for GitHub API calls, skipping its native App JWT exchange, and verifies Connect-forwarded webhooks by their Vercel OIDC signature instead of a GitHub webhook secret. Token rotation, refresh, and multi-installation tenancy stay inside Connect, so there is no `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, or `GITHUB_WEBHOOK_SECRET` to manage.

### Bring your own GitHub App

To run a GitHub App you manage yourself, pass its credentials directly instead:

```ts title="agent/channels/github.ts"
import { githubChannel } from "eve/channels/github";

export default githubChannel({
  botName: "my-agent",
  credentials: {
    appId: process.env.GITHUB_APP_ID,
    privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
    webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
  },
});
```

Every field falls back to an env var, so you can drop the `credentials` block entirely once these are set:

```bash
GITHUB_APP_ID=...            # GitHub App id
GITHUB_APP_PRIVATE_KEY=...   # GitHub App private key (PEM)
GITHUB_WEBHOOK_SECRET=...    # verifies the webhook signature
GITHUB_APP_SLUG=...          # supplies botName when it is not set in config
```

`appId`/`privateKey`/`webhookSecret` also take a lazy resolver function if you'd rather fetch them on demand.

Point the GitHub App webhook URL at `https://<deployment>/eve/v1/github`. For comment-invoked turns, subscribe to `issue_comment` and `pull_request_review_comment`; add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. After installing the App for the repository, a new comment that includes `@botName` starts a turn. This is a text invocation token, not a GitHub-native mention: GitHub may display the App as `botName[bot]`, but it may not autocomplete or link `@botName`.

## How the channel handles messages

### Dispatch

Inbound hooks return `{ auth }` to dispatch, or `null` to ignore. Use `defaultGitHubAuth(ctx)` to derive auth from the actor.

```ts
import { defaultGitHubAuth, githubChannel } from "eve/channels/github";

export default githubChannel({
  botName: "my-agent",
  // Replaces the default invocation-token gate. ctx.conversation.kind is "issue", "pull_request", or "review_thread".
  onComment: (ctx, comment) => ({ auth: defaultGitHubAuth(ctx) }),
  // Opt in; no default dispatch on these events.
  onIssue: (ctx, issue) => (issue.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null),
  onPullRequest: (ctx, pr) => (pr.action === "opened" ? { auth: defaultGitHubAuth(ctx) } : null),
  onCheckSuite: (ctx, suite) =>
    suite.action === "completed" &&
    suite.conclusion === "failure" &&
    suite.app.slug === "github-actions" &&
    suite.pullRequests.length > 0
      ? {
          auth: defaultGitHubAuth(ctx),
          context: [`Triage failed check suite ${suite.checkSuiteId} at ${suite.headSha}.`],
        }
      : null,
});
```

The CI hooks expose normalized `action`, `status`, `conclusion`, `app.slug`, `headSha`, and `pullRequests` fields, plus `checkSuiteId`, `checkRunId`, or `workflowRunId`. `workflow_run` is a GitHub Actions-only event, so its normalized `app.slug` is `"github-actions"`. A dispatched CI turn is anchored to the first number in `pullRequests`; the hook still runs when the array is empty, but it must return `null` because there is no issue or PR thread for the session.

### Delivery

When a turn starts, the channel adds an `eyes` reaction to the triggering comment (turn this off with `progress: { reactions: false }`). The reply comes back as a comment, on the timeline or in the review thread, and splits across multiple comments when it runs long. If the turn fails, you get a short error comment carrying an error id.

### Human-in-the-loop (HITL)

GitHub comments have no interactive button or card affordance. A human-in-the-loop (HITL) `input.requested` event is posted as a comment prompt, and the user's reply comment maps back to the pending input request. Declare an `events["input.requested"]` handler to customize the prompt.

### Proactive sessions

Start a session without an inbound comment invocation through `to(github, target).send(message, { auth })` from a schedule `run` handler, or `ctx.to(github, target).send(message, { auth })` from another channel. The target requires `owner`, `repo`, and exactly one of `issueNumber` or `pullRequestNumber`.

### Attachments

Inbound file attachments are not supported on this channel today. Repository contents reach the agent through the sandbox checkout below, not as message attachments.

### PR context

Summon the agent on a PR and it always sees the diff. PR metadata and the changed-file patch land in `context`. Large generated files still appear in the list, but their patch body is dropped; add more paths to the skip list with `pullRequestContext.excludedFiles`.

### Sandbox checkout

Before the first model call, every triggered turn checks out the relevant ref into the sandbox, so `read_file`/`glob`/`grep`/`bash` all run against the real tree. The installation token never enters the sandbox. `git` fetches a token-free URL, and the platform injects auth on egress at the firewall. That requires a firewall-capable backend (Vercel); the local backend skips checkout. Within a session, checkout is incremental across turns.

### Arbitrary API calls

For anything the channel doesn't wrap, call `ctx.github.request({ method, path, body })`. It carries installation-token auth.

## What to read next

- [Channels overview](./overview): the channel contract and every built-in channel
- [Auth & route protection](../guides/auth-and-route-protection): authenticating inbound traffic
