---
title: "GitHub"
description: "Reach your agent from GitHub App webhooks, with @mention dispatch, PR diff context, and sandbox checkout."
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. See [Channels](./overview) for the contract this builds on.

## Add the channel

```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
