> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub

> Connect a GitHub organization and index every covered repository into the code graph.

Connect a GitHub **organization** (or user account) to Cognee by installing a GitHub App into it. Every repository the installation covers is cloned and indexed into the code graph under a single dataset, and stays fresh through webhooks — a push to a default branch re-indexes that repository, and repositories added to the installation are picked up automatically.

<Info>
  This integration is for self-hosted Cognee deployments — you create and own the GitHub App, so it lives in your own GitHub organization and talks only to your own Cognee backend.
</Info>

**Ships on `dev`.** The connector landed after `1.5.3`, so it is not in a tagged release yet — run a `dev` checkout (`git checkout dev && uv sync`) until the next release.

## What It Does

* **One install covers the whole org.** The org admin installs the app once and picks which repositories it can see. Cognee indexes all of them — there is no per-repository connect step.
* **Code graph, not documents.** Repositories go through the deterministic [code-graph pipeline](/guides/code-graph): typed module/symbol/route/storage/service facts and their edges, with **no LLM or embedding calls**. Results are reachable through [`SearchType.CODE`](/python-api/search-type) only — the route produces no chunks and no embeddings, so `GRAPH_COMPLETION`, `CHUNKS`, and `RAG_COMPLETION` do not cover this content.
* **No stored GitHub tokens.** The credential Cognee persists for a GitHub installation carries an **empty token payload**. Access tokens (\~1 hour) are minted on demand from the app's private key when a sync runs, used, and discarded.
* **Read-only.** The app needs `Contents: Read-only` and nothing else. Cognee never writes to GitHub.

## Prerequisites

* A Cognee `dev` checkout — see the version note above.
* A running Cognee backend reachable over **HTTPS from the public internet**: GitHub delivers webhooks and the OAuth redirect to it and will not call `localhost`. For local development use a tunnel (ngrok or similar) — see the [Slack integration's ngrok section](/integrations/slack-integration#local-development-ngrok-tunnel), which applies verbatim; for production this is your deployment's normal public domain.
* Permission to create and install a GitHub App in the target organization (org owner, or a repository admin if the org allows it).
* `git` available on the Cognee host — repositories are shallow-cloned into `~/.cognee/repos`.

## Setup

### 1. Create the GitHub App

Go to [github.com/settings/apps](https://github.com/settings/apps) → **New GitHub App** and configure:

| Setting                                                    | Value                                                                                                                                                                                                                                                                 |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Callback URL**                                           | `https://<public-host>/api/v1/integrations/github/callback`                                                                                                                                                                                                           |
| **Request user authorization (OAuth) during installation** | **Enabled** — required. Cognee never trusts the `installation_id` in the redirect on its own; it exchanges the OAuth `code` for a user token and confirms that user actually has access to the installation before binding it. Without this the install always fails. |
| **Webhook URL**                                            | `https://<public-host>/api/v1/integrations/github/events`                                                                                                                                                                                                             |
| **Webhook secret**                                         | Any high-entropy string — this becomes `GITHUB_WEBHOOK_SECRET`.                                                                                                                                                                                                       |
| **Repository permissions**                                 | `Contents: Read-only`.                                                                                                                                                                                                                                                |
| **Subscribe to events**                                    | **Push** and **Installation repositories**.                                                                                                                                                                                                                           |

Then, from the app's settings page, collect: the numeric **App ID**, the app's **slug** (the `<slug>` in `github.com/apps/<slug>`), a generated **private key** (`.pem` download), and the **Client ID** / **Client secret**.

<Warning>
  Create a **separate app per environment** (local dev, staging, production). One app has one callback URL and one webhook URL, so pointing a shared app at a laptop routes another environment's deliveries there.
</Warning>

### 2. Configure Cognee

Copy the app's values into your backend's `.env` (the same block is in `.env.template`):

| Variable                      | Value                                                                                                                                                                                                                                                                                                                                                                                           |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GITHUB_APP_ID`               | The app's numeric id, from its settings page. Used to sign the app JWT that mints installation tokens.                                                                                                                                                                                                                                                                                          |
| `GITHUB_APP_SLUG`             | The app's URL slug (`github.com/apps/<slug>`) — used to build the installation URL the connect flow redirects to.                                                                                                                                                                                                                                                                               |
| `GITHUB_APP_PRIVATE_KEY`      | The downloaded PEM private key. Literal `\n` escapes are accepted, so the whole key fits on one line in an env file. Must be an **RSA** key (GitHub's generated keys are).                                                                                                                                                                                                                      |
| `GITHUB_CLIENT_ID`            | From the app's settings page.                                                                                                                                                                                                                                                                                                                                                                   |
| `GITHUB_CLIENT_SECRET`        | From the app's settings page.                                                                                                                                                                                                                                                                                                                                                                   |
| `GITHUB_WEBHOOK_SECRET`       | The webhook secret you set on the app. It verifies every inbound delivery's `X-Hub-Signature-256` **and** signs the OAuth `state` parameter — both server-side-only uses of the same secret.                                                                                                                                                                                                    |
| `GITHUB_FRONTEND_BASE_URL`    | Your frontend's origin, e.g. `http://localhost:3000` — where the callback redirects the browser when the install finishes.                                                                                                                                                                                                                                                                      |
| `INTEGRATION_CREDENTIALS_KEY` | Not GitHub-specific — the AES-256-GCM key (32 raw bytes, base64-encoded) the integrations framework encrypts stored credentials under. GitHub's payload is empty, but the row still goes through the encrypted store, so a missing key fails the connect. Generate one with `openssl rand -base64 32`. `INTEGRATION_CREDENTIALS_KEYS` (a JSON keyring) is the preferred, rotation-capable form. |

<Note>
  There is no `GITHUB_REDIRECT_URI`. Unlike Slack, the GitHub App install flow takes its callback URL from the app's own settings rather than from a query parameter, so the redirect URL is configured in one place only — the app.
</Note>

Every one of these is checked at use time, not at boot: a deployment with GitHub unconfigured still starts normally, and the first call that needs a missing value fails with `GITHUB_<NAME> is not configured` rather than a confusing downstream error. `POST /authorize` turns that into a **503** (`github integration is not configured on this server`) instead of a 500.

Restart the backend after any `.env` change.

### 3. Install and Connect

The backend ships without frontend wiring for GitHub in this release — the aggregate `GET /api/v1/integrations/status` reports the provider generically, but there is no **Connect** button yet. Drive the flow through the API:

```bash theme={null}
# 1. Mint the install URL (authenticated as the Cognee user who will own the data)
curl -X POST https://<public-host>/api/v1/integrations/github/authorize \
  -H "Authorization: Bearer <your-cognee-token>"
# {"authorizeUrl":"https://github.com/apps/<slug>/installations/new?state=..."}
```

Open that URL in a browser, pick the organization, choose which repositories the app may access, and approve. GitHub redirects back to `/api/v1/integrations/github/callback`, which binds the installation to your Cognee account and then sends the browser to `<GITHUB_FRONTEND_BASE_URL>/integrations?github=connected`.

<Warning>
  **Install from the minted URL, not from the app's public install page.** The signed `state` is what binds the installation to a Cognee account, and it expires **10 minutes** after `/authorize` returns. An install started without it (or after it expires) lands on `?github=error_invalid_state` and stores nothing.
</Warning>

The initial sync of every covered repository is kicked off **detached**, after the redirect. The browser returns immediately; repositories appear in the graph as the clones and pipeline runs finish, which for a large org can take a while.

## What Gets Indexed

Every repository the installation covers lands in **one dataset per installation**, named `github_<account>`: the org (or user) login lowercased, with every run of non-alphanumeric characters collapsed to `_`. `Acme-Corp` becomes `github_acme_corp`. One dataset per org rather than per repository means [backend access control](/core-concepts/multi-user-mode/multi-user-mode-overview) isolates at the org boundary — per-repo datasets would mean one isolated database per repository.

The dataset is owned by the Cognee user who completed the install. Query it with `SearchType.CODE`:

```python theme={null}
import cognee
from cognee import SearchType

results = await cognee.search(
    query_type=SearchType.CODE,
    query_text="",
    datasets=["github_acme_corp"],
    code_query={"operation": "query_facts", "kinds": ["module", "symbol", "route"], "limit": 20},
)
```

See the [Code Graph guide](/guides/code-graph) for the full set of `code_query` operations (`explore`, `traverse`, `find_path`, `impact_analysis`, `delta`).

## Keeping It in Sync

After the initial sync, webhook deliveries to `POST /api/v1/integrations/github/events` keep the graph fresh. Coverage is deliberately narrow in this first cut:

| Event                                  | Behavior                                                                                                                                                                                                                                                       |
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `push`                                 | Re-indexes that repository — but only for pushes to its **default branch**. The clone is a shallow copy of the default branch, so other refs change nothing Cognee holds.                                                                                      |
| `installation_repositories`            | Repositories **added** to the installation are indexed. Repositories **removed** are logged only — indexed data is retained, because deleting it on a webhook would be a silent, destructive surprise. Drop it yourself with [`forget()`](/python-api/forget). |
| `installation` (`deleted` / `suspend`) | Revokes the stored credential, so no further tokens are minted.                                                                                                                                                                                                |

Every delivery is authenticated by an HMAC-SHA256 over the raw request body, keyed with `GITHUB_WEBHOOK_SECRET` and compared against `X-Hub-Signature-256`. That signature is the entire auth model for the route — it is unauthenticated by design, since GitHub cannot send a bearer token. A bad or missing signature is a **401**. Cognee acks the delivery as soon as the signature checks out and handles it detached, so GitHub's delivery timeout is never in play no matter how long a re-index takes.

Deliveries are idempotent and safe to redeliver: unchanged repository snapshots are skipped, and revokes are no-ops on already-revoked rows. A delivery for an installation Cognee doesn't know (or has revoked) is logged and dropped rather than erroring — GitHub gives no ordering guarantee, so an `installation.created` arriving before the callback stored the credential is a normal, self-healing state.

<Note>
  The installation token minted for a sync lives about an hour and repositories are cloned sequentially. A very large installation can outlive its token mid-batch; the affected repositories surface as per-repo errors, and the next push (or a manual re-sync) picks them up with a fresh token.
</Note>

## How Access Works

* **Nothing durable is stored but the installation id.** Tokens are minted on demand: the app's private key signs a \~9-minute RS256 JWT, which mints a \~1-hour installation token scoped to that installation's repositories.
* **The redirect's `installation_id` is never trusted on its own.** It arrives on an unauthenticated endpoint and is a small, guessable integer. The OAuth `code` is exchanged for a user token, that user's access to the installation is confirmed against `GET /user/installations`, and only then is the authoritative installation record fetched with the app JWT. Without the check, anyone could bind another org's installation — and read access to its private repositories — to their own account.
* **One installation, one Cognee account.** An installation already connected to a different user is refused (`?github=error_already_connected`) rather than silently reassigned.
* **Tokens never ride a URL.** Clone URLs stay credential-free (`https://github.com/org/repo.git`); the token reaches `git` through environment-level config instead. The persisted git remote, the recorded `source` on each result item, log lines, and git error output all use the credential-free form, so a token cannot leak through any of them.

## Disconnect vs. Uninstall

They are not the same, and neither deletes indexed data:

* **Disconnect in Cognee** — `DELETE /api/v1/integrations/github/connection` — marks the stored credential revoked. No further installation tokens are minted, so syncs stop. The app stays installed on GitHub, and the `github_<org>` dataset stays exactly as it is.
* **Uninstalling the app on GitHub** fires the `installation` `deleted` webhook, which revokes the credential server-side — the same end state, reached from the other side. This is what actually cuts off GitHub's access.

Cognee deliberately does **not** uninstall the app on your behalf during a disconnect: the GitHub-side equivalent of a token revoke is deleting the installation for the entire org, which is too destructive for a per-account action. To remove the indexed data itself, use [`forget()`](/python-api/forget).

## Troubleshooting

1. **`POST /authorize` returns 503 "github integration is not configured on this server"**: `GITHUB_APP_SLUG` or `GITHUB_WEBHOOK_SECRET` (it signs the state) is unset. The backend log names the exact variable. A missing `GITHUB_FRONTEND_BASE_URL` surfaces later, as the same 503 from the callback instead.
2. **Redirected to `?github=error_invalid_state`**: the install didn't start from a freshly minted `/authorize` URL, or more than 10 minutes passed between minting it and approving the install. Mint a new one.
3. **Redirected to `?github=error_exchange_failed`**: the callback never 500s — it catches everything and redirects, and the traceback is in the **backend log**. Usual causes: "Request user authorization (OAuth) during installation" is off on the app (so no `code` arrives and the exchange has nothing to trade), mismatched `GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET`, a `GITHUB_APP_PRIVATE_KEY` that isn't an RSA PEM, or a missing `INTEGRATION_CREDENTIALS_KEY`.
4. **Redirected to `?github=error_already_connected`**: this installation is bound to another Cognee user. Disconnect it there first.
5. **Connect succeeds but no repositories appear**: the initial sync runs detached and can take a long time on a large org — check the backend log for `Syncing N GitHub repositories for <org> into dataset github_<org>`. `N` is what the app can actually see, so if it's `0` or too low, widen the installation's repository access on GitHub (that fires `installation_repositories` and syncs the additions).
6. **Webhook deliveries show 401 in GitHub's "Recent Deliveries"**: `GITHUB_WEBHOOK_SECRET` doesn't match the secret set on the app — easy to hit when juggling more than one app. Re-copy it and restart the backend.
7. **Webhook deliveries show 404**: the provider name in the URL is wrong, or the backend is running a build without the GitHub connector registered. The route is `/api/v1/integrations/github/events`.
8. **A push doesn't re-index anything**: only pushes to a repository's **default branch** trigger a re-index. Pushes to other branches are ignored by design.
9. **`GRAPH_COMPLETION` or `CHUNKS` search finds nothing from a connected repo**: expected — the code route stores graph facts only, with no chunks or embeddings. Use `SearchType.CODE`.

## Related

<CardGroup cols={2}>
  <Card title="Code Graph" icon="code" href="/guides/code-graph">
    The pipeline this connector feeds, and every `SearchType.CODE` operation.
  </Card>

  <Card title="Slack" icon="messages-square" href="/integrations/slack-integration">
    The other first-party OAuth connector, sharing the same credential store and connect flow.
  </Card>
</CardGroup>
