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

# Register the GitHub App

> Deployment-time guide for the GitHub App the organization connects, covering registration, permissions, the GITHUB_LANGY env vars, GitHub Enterprise Server, restricted networks, install, verify, and disconnect.

One GitHub App serves the whole organization connection, and two features use it:

* **Langy pull requests.** Langy opens pull requests through the App. They are authored by the App and credit the requesting user with a `Co-authored-by` trailer and a `Requested by @<login> via LangWatch` note in the body.
* **Coding-agent pull request linkage.** LangWatch matches each coding-agent session to the pull request its branch became, so the Pull Requests page can report what each pull request cost in assistant usage. This part only reads.

There is no per-user OAuth and no long-lived stored token: the app private key is the only credential, it lives only in the control-plane env, and installation tokens are minted on demand, cached in Redis a hair under their 1 hour lifetime, and never written to the database.

To set it up: register a GitHub App, put its id, private key, webhook secret, and slug into the deployment env, and have org admins install it on the repos LangWatch may touch from **Settings > Integrations**.

<Info>
  **Also check:** [What Langy can do in your GitHub](/docs/langy/security/github-access), [Setup](/docs/self-hosting/langy/setup), [Environment variables](/docs/self-hosting/langy/environment-variables).
</Info>

<Note>
  Registering the app is independent of the Langy runtime, which the umbrella chart deploys by default. See [Setup](/docs/self-hosting/langy/setup) first if you have not enabled the pod yet. If the private key is unset, the GitHub feature is silently off and Langy returns findings without opening PRs.
</Note>

## Register the app

Go to GitHub, then your **org settings > Developer settings > GitHub Apps > New GitHub App**. Self-hosted registration is manual for now; the App Manifest one-click flow is a deferred follow-up.

Fill in these fields:

| Field                                                  | Value                                                                               |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| App name                                               | `LangWatch Langy` (or per environment, e.g. `LangWatch Langy (dev)`)                |
| Homepage URL                                           | `https://app.langwatch.ai` (or your control-plane URL)                              |
| Setup URL                                              | `<BASE_URL>/api/github/setup`                                                       |
| Redirect on update                                     | ON (so re-configuring an install returns to the Setup URL)                          |
| Request user authorization (OAuth) during installation | OFF. Langy is bot-authored, no user OAuth.                                          |
| Webhook > Active                                       | ON (optional, see [Restricted networks](#restricted-networks))                      |
| Webhook URL                                            | `<BASE_URL>/api/github/webhook`                                                     |
| Webhook secret                                         | Generate one (`openssl rand -hex 32`). You set it as `GITHUB_LANGY_WEBHOOK_SECRET`. |
| Where can this GitHub App be installed?                | Any account (recommended) or only this org                                          |

<Note>
  `/api/github-langy/setup` and `/api/github-langy/webhook` are the earlier paths. They stay mounted on the same handlers, so an App already registered with them keeps working. Use the paths above for a new registration.
</Note>

Set the repository permissions to exactly these, nothing more. Tighter scope means a smaller blast radius.

| Permission    | Access       |
| ------------- | ------------ |
| Contents      | Read & write |
| Pull requests | Read & write |
| Metadata      | Read         |

Under **Subscribe to events**, select **Pull request**. GitHub sends the installation lifecycle events (`installation`, `installation_repositories`) on its own. Subscribe to nothing else.

The `pull_request` event links a pull request to its coding-agent sessions as soon as GitHub announces it. Without the event, linkage still works, but it waits for the periodic sweep, which can take up to a day for a branch that has been asked about a few times already.

Click **Create GitHub App**. On the resulting page:

* copy the **App ID**
* note the **app slug** (the last segment of the app's own URL)
* **Generate a private key** and download the `.pem`

## Plumb the secrets

Add these to the control-plane env (`.env` or your secret manager). All are optional in the schema (`platform/app/src/env-create.mjs`); when the private key is unset the feature is silently off, the connect card reports the integration as unavailable, and no token can be minted.

```bash title=".env" theme={null}
GITHUB_LANGY_APP_ID="<App ID from registration>"
GITHUB_LANGY_APP_SLUG="<app slug from the app URL>"
GITHUB_LANGY_WEBHOOK_SECRET="<the webhook secret you generated>"
# The private key PEM. In a single-line env value, escape newlines as \n;
# the token service normalises them back.
GITHUB_LANGY_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
# Only for GitHub Enterprise Server. Unset means github.com.
# GITHUB_LANGY_HOST="github.acme-corp.internal"
```

<Warning>
  `GITHUB_LANGY_PRIVATE_KEY` is the only credential for the whole GitHub integration and grants write access to installed repos. Keep it in a secrets manager, never in version control, and never let it reach the Langy agent pod. It stays in the control plane.
</Warning>

There is no client id or secret; the bot-authored flow uses no user OAuth. `CREDENTIALS_SECRET` (already required for any production install) signs the install round-trip's state, so nothing new is needed there.

For the full env-var reference, see [Environment variables](/docs/self-hosting/langy/environment-variables#github).

### Helm

The chart has no `GITHUB_LANGY_*` keys of its own. Set the variables with `app.extraEnvs` or `app.extraEnvFrom`.

`GITHUB_LANGY_PRIVATE_KEY` is a multi-line PEM, so put all four values in a Secret you manage and reference it with `app.extraEnvFrom`. That keeps the PEM out of your values file and lets you rotate it without a chart change.

```yaml title="values.yaml" theme={null}
app:
  extraEnvFrom:
    - secretRef:
        name: langwatch-github-app
  extraEnvs:
    # Only for GitHub Enterprise Server.
    - name: GITHUB_LANGY_HOST
      value: github.acme-corp.internal
```

Set the same values on `workers` when you run the LangWatch workers as their own deployment. The periodic branch recheck runs there, and without the private key it cannot mint a token.

The LangWatch `workers` deployment is part of the control plane. It is not the Langy agent pod, which runs customer code in a sandbox and never receives the private key.

## GitHub Enterprise Server

One LangWatch instance connects to exactly one GitHub. To use GitHub Enterprise Server instead of github.com:

1. Register the App on the Enterprise Server instance, at `https://<HOSTNAME>/settings/apps/new` under your organization or site admin settings. An App registered on github.com cannot be installed on an Enterprise Server, so this is a separate registration with its own id, key and slug.
2. Set `GITHUB_LANGY_HOST` to the Enterprise Server hostname, for example `github.acme-corp.internal`. Give the hostname only, with no scheme and no path.
3. Set the other four `GITHUB_LANGY_*` variables from the new registration.

LangWatch then derives every address from that host: the REST API at `https://<HOSTNAME>/api/v3`, the install link at `https://<HOSTNAME>/github-apps/<slug>/installations/new`, and the uninstall link in settings. Egress from the control plane goes to the Enterprise Server host instead of `api.github.com`.

Sessions are matched by the host their git remote reports, so an instance bound to an Enterprise Server links sessions on that host and leaves github.com sessions unlinked.

<Note>
  `GITHUB_LANGY_HOST` covers the control plane: the connection, token minting, and coding-agent pull request linkage. Langy's own pull requests still go to github.com, because the worker clones with the `gh` command line, which reads its own host setting. Enterprise Server support for Langy pull requests is separate work.
</Note>

## Restricted networks

GitHub must be able to reach `<BASE_URL>/api/github/webhook` to deliver webhooks. Many self-hosted instances sit behind a VPN or a private network, where that is not true.

**The webhook is optional.** An instance with no inbound path connects and works. Connecting is a browser redirect through `/api/github/setup`, so only the admin's own browser has to reach the instance, not GitHub. You can leave **Webhook > Active** off, or leave it on and accept the failed deliveries.

What changes without webhooks:

| Behavior                                         | With webhooks                     | Without webhooks                                                                                                                                                               |
| ------------------------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| A pull request appears on the Pull Requests page | Within seconds of it being opened | On the next outbound recheck: about 15 to 25 minutes while sessions keep running on the branch, up to 24 hours for a branch that went quiet before its pull request was opened |
| Uninstall and suspend                            | Reconciled at once                | Reconciled late. The next live read gets a 404 from GitHub and removes the stale record                                                                                        |
| Repository selection changes                     | Reconciled at once                | Refreshed on the next live resolve                                                                                                                                             |

Everything else is outbound only and is not affected: minting installation tokens, Langy opening pull requests, the post-connect backfill, and the live pull request status the page reads.

## Install the app

An org admin installs the app from **Settings > Integrations > GitHub > Install the LangWatch GitHub App**, or from the in-chat card the first time Langy needs GitHub access. GitHub walks them through choosing the repositories, then redirects to the Setup URL, which records the installation against their LangWatch organization.

The installation is the access boundary: Langy can only open PRs on repositories the app is installed on. A single LangWatch organization can install the app on several GitHub accounts; each installation is listed separately in settings.

## Verify

In a session, ask Langy something like "open a PR on `<a repo the app is installed on>`". You should see:

<Steps>
  <Step title="Install card (if not yet installed)">
    The "Install the LangWatch GitHub App" card appears in chat if the org has not installed the app yet.
  </Step>

  <Step title="Chat continues">
    After installing, the popup closes and the chat continues.
  </Step>

  <Step title="PR card">
    Langy clones the repo into the per-session worker home, branches, commits, pushes, and posts the PR URL, rendered as a PR card. The PR is authored by the app, with the requester credited as co-author.
  </Step>
</Steps>

To check the `pull_request` subscription, open a pull request on a repository the app is installed on, from a branch a coding-agent session ran on. GitHub records the delivery under **Advanced > Recent Deliveries** on the app registration, and the pull request appears on the LangWatch Pull Requests page without waiting for the sweep.

If anything goes wrong, the audit log (`langy.github.install`, `langy.github.disconnect`) and Sentry capture the failure paths.

## Disconnect

Go to **Settings > Integrations > Disconnect**, which opens GitHub's uninstall page (GitHub cannot be uninstalled via the API). Once GitHub confirms, the `installation.deleted` webhook removes the local record.

## Operational notes

* **Token in the agent pod, not on disk.** Langy workers receive a 1-hour installation token via `GH_TOKEN` env. The github skill wires `git config credential.helper '!gh auth git-credential'` so git pushes read it from env, with no `.gitconfig` and no `.git-credentials`. The clone directory lives inside the per-worker home that the idle reaper deletes (within 10 min).
* **Nothing at rest.** No token or refresh token is stored control-plane side. The app private key is the only credential; it never leaves the control plane and never reaches the Langy agent pod. Tokens are minted per turn, cached in Redis a hair under their 1h lifetime, and self-expire. The cache key carries the GitHub host, so two GitHub instances cannot share an entry.
* **Least privilege at mint time.** Tokens are minted with only `contents:write` and `pull_requests:write`, scoped to the installation's repositories (or a single repository when the turn targets one).
* **Sticky worker tokens.** A worker spawned with token T keeps T until the idle reaper kills it. Removing the installation cuts off new turns immediately; a live worker may hold a token for up to the idle TTL (within 10 min) or until it self-expires (within 1h).
* **Egress.** The control plane reaches `api.github.com` to mint installation tokens and read installation metadata, or `<GITHUB_LANGY_HOST>` when one is set. Workers need `github.com`, `api.github.com`, and `codeload.github.com` to clone and push, which requires outbound egress to be enabled. See [Networking and egress](/docs/self-hosting/langy/networking-and-egress).
