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

The GitHub channel lets the agent work directly on a repository. Someone `@mentions` it in an issue, PR, or review comment, and the agent answers right there in the thread, with the PR diff already in context and the repo checked out into the sandbox. It 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.

## Set up Connect

Create a GitHub Connect client and copy its UID (e.g. `github/my-agent`), then attach this project as the trigger destination at eve's GitHub route:

```bash
npm install -g vercel@latest
vercel connect create github --triggers
vercel connect detach <uid> --yes
vercel connect attach <uid> --triggers --trigger-path /eve/v1/github --yes
```

The `create` step provisions the GitHub App and a trigger destination at the default Connect path. `detach` then `attach --trigger-path /eve/v1/github` re-points the trigger at the eve GitHub route, since eve does not serve the default Connect path. `--triggers` makes Connect receive the App's webhooks, verify them, and forward them to your deployment. During registration, subscribe to `issue_comment` and `pull_request_review_comment` for mention-driven turns — the managed App defaults to `pull_request` only — and add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. You can also create the client from the [Connect dashboard](https://vercel.com/d?to=/%5Bteam%5D/~/connect&title=Go+to+Connect).

## Add the channel

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

```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 mention-driven 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. A comment that `@mention`s `botName` starts a turn.

## 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 @mention 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 mention through `receive(github, { message, target, auth })` from a schedule `run` handler, or `args.receive(github, ...)` 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
