# GitHub

> Connect GitHub twice — a read token for the agents and a Contents read-and-write token for GitOps pull requests — and capture pushes and merges with a watch list or a webhook.

_How-to · Updated 2026-10-23 · Describes release 0.1.164_

## Overview {#overview}

GitHub is where the **Change** agent reads commits and pull requests for last-change checks and where the compliance controls read branch protection and GitOps drift; the **Incident** agent reads it too. It is also the GitOps remediation path: an approved fix is a pull request on your repository, opened through a write-role connection, and nothing changes in the running estate until a person merges it. Pushes and merged pull requests reach the change ledger through a polled watch list, a webhook, or both.

The catalog marks this type write-capable, which is why the recommended setup is two connections: a fine-grained read token the agents investigate with, and a separate token with **Contents: Read and write** on the one repository used for pull requests.

## Setup {#setup}

### Before you start {#before-you-start}

- The `datasources.manage` permission; the built-in owner and operator roles carry it.
- A fine-grained personal access token with read access to the intended repositories and, for fixes, a second read-and-write token ([Installation](#installation)).

### Installation {#installation}

In GitHub open **Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token**. For the read connection choose the resource owner, an expiry and only the repositories needed, with **Contents: Read** and **Pull requests: Read**; add **Issues: Read** only when issue context matters. For the write connection create a second token with **Contents: Read and write** and **Pull requests: Read and write** on the remediation repository; workflow-file edits also need **Workflows: Write**. Do not grant organisation administration. A token's repository selection, not the platform, decides what it can see.

### Configuration {#configuration}

1. Open **Settings · Data in → Data sources** and select **Connect datasource**.
2. Under **Type**, choose **GitHub**. Enter a **Connection name**; the connected list shows it, and per-connection agent tools are named after it.
3. Fill the fields:

| Field (portal label) | API key | Value |
|---|---|---|
| GitHub API base URL | `base_url` | `https://api.github.com`, or `https://<your-enterprise-host>/api/v3` |
| Access token | `token` | Sent as `Authorization: Bearer`; stored write-only |
| Repositories the change agent watches (optional) | `repos` | `acme/payments, acme/infra-*`: comma- or newline-separated `owner/name`, patterns allowed on GitHub |

4. Optionally, open **Optional · describe this environment** and say in **What should AIOps know about it?** what this system is and what matters.
5. For the agents' connection, choose **Investigate only** under **Use this connection for** and select **Connect and verify**. For the pull-request connection choose **Approved changes only (separate credential)** under **Use this connection for**, tick **Allow use for approved plans** when fixes may run on it, and save with **Save remediation credential**. A workspace owner can then record **Repository ownership** on it: the repository key, its base branch and the resource identities it manages, one per line. That is ownership evidence, not approval of any change.

The read connection through the API (`POST /api/aiops/{tenant_id}/datasources`, requires `datasources.manage`; the token goes in `secret_values`):

```json
{
  "type_key": "github",
  "name": "github-read",
  "description": "Platform repositories; acme/infra holds the Kubernetes manifests Argo CD applies.",
  "config": {
    "base_url": "https://api.github.com",
    "repos": "acme/payments, acme/infra"
  },
  "secret_values": {
    "token": "<fine-grained PAT, Contents: Read>"
  },
  "role": "read",
  "enabled": true
}
```

The pull-request connection is the same body with the read-and-write token, `"role": "write"` and `"allow_approved_execution": true`.

### Validation {#validation}

**Connect and verify**, and **Test** on the row later, sends `GET /rate_limit` with the token. **You should see** a dialog titled `<connection name> is connected` with **Connected — verified from the control plane**, and later in the row's **Last test** `ok · HTTP 200 /rate_limit — {"resources": …`; a bad or expired token answers `HTTP 401`. GitHub serves that endpoint without authentication too, so a green result does not prove private-repository access: open one intended repository through the agent, or select **Audit perms**, before relying on it. A write connection saves without a probe; its token is exercised by the first approved pull request.

### If something fails {#if-something-fails}

The dialog shows **Needs a fix**, what to fix and the check's own detail; the row is saved either way. Select **Edit**, correct the field the detail names and save again, or select **Retry** once a cause outside the form (network, vendor-side permission) is fixed. Every verdict this connector produces, and what to do about it, is in [Troubleshooting](#troubleshooting) below.

## Data collected {#data-collected}

### Tools the agent gets {#tools}

- `<name>_request` — the HTTP tool against the API base URL with `Authorization: Bearer <token>`: `method`, `path`, `query`, `body`, `content_type`. Last-change checks read `GET /repos/<owner>/<repo>/commits?since=…`, `GET /repos/<owner>/<repo>/pulls?state=closed` and `GET /repos/<owner>/<repo>/contents/<path>`; branch protection comes from `GET /repos/<owner>/<repo>/branches/<branch>/protection`.

Only a read-role connection renders tools; the write-role connection is invisible to every agent. Responses are capped at 20,000 characters per call.

### Read surface {#read-surface}

`GET` and `HEAD` run freely. A `POST /graphql` is classified from its body: a document with exactly one query operation runs, a mutation, a subscription, a batch or an unreadable document is refused. Every other `POST`, `PUT`, `PATCH` and `DELETE` — creating issues, comments, branches or files — is refused in an investigation, never offered for approval: `Investigation connections are read-only. This operation was not executed; prepare an incident change using a write connection and obtain approval.` The full rule table is under **The read/write gate in detail** in [Connections and remediation access](/help/read-and-write-roles.md#read-role-vs-write-role).

### Changes captured {#changes-captured}

Two ways in, complements rather than alternatives:

- **Watch list.** The **Repositories** field names what the change agent polls for commits: `owner/name`, a pasted browser or clone address (`https://github.com/acme/payments.git` becomes `acme/payments`), and on GitHub patterns such as `acme/platform-*`. Twenty-five repositories per connection are polled each tick; the rest are reported, never silently dropped. Only enabled read-role connections are polled, and an empty field is a real answer: that connection watches nothing.
- **Webhook.** In the repository open **Settings → Webhooks → Add webhook**, set the payload URL to `https://<your-host>/webhook/github/<workspace-slug>/<webhook-token>`, the content type to `application/json`, and under *Let me select individual events* choose **Push** and **Pull requests**. Only a merged pull request is recorded; one closed without merging changed nothing. A webhook arrives at once, the watch list minutes to hours later, and a commit seen twice is folded into one change record. The payload fields and response counters are in the [webhook reference](/help/webhook-reference.md#change-sources).

### Pull requests as fixes {#pull-requests}

A `gitops_pr` route on the write connection turns an approved change into a branch and a pull request. Its parameters: `repo` (`owner/name`), the file as `path` or a `path_template`, `base_branch` (default `main`), `branch` (default `aiops/remediation-<12 characters of the incident id>`), and `pr_title_template` (default `AIOps remediation: <incident title or path>`); the content is either a whole file or a find-and-replace `edit` the agent proposed, and a redacted source is refused (`gitops_pr: redacted content is not executable; obtain an exact fresh source`). The pull request body says what it is and that no live infrastructure was touched; the platform watches for its merge and validates after it. How approvals, tiers and certification apply is in [Set up write connections](/help/setup-write-connections.md).

## Troubleshooting {#troubleshooting}

| What you see | Cause | What to do |
|---|---|---|
| `failed · HTTP 401 /rate_limit` | A revoked, expired or mistyped token. | Generate a new fine-grained token and rotate it on **Edit**. |
| **Test** is green, but a repository read answers `HTTP 404` | The token's repository selection does not include it; GitHub answers 404, not 403, for a repository the token cannot see. | Edit the token's repository access. |
| A pull request fails with `gitops_pr: <step> failed: HTTP 403 …` | The write token lacks **Contents: Read and write**, or the branch is protected against it. | Fix the token's permissions on the remediation repository. |
| `gitops_pr: route params missing 'repo'` or `gitops_pr: missing 'path'/'path_template'` | The route is incomplete. | Fill `repo` and `path` on the route. |
| `gitops_pr: the edit anchor is not in <path> — …` | The file changed since the agent read it. | Re-run the analysis so the edit matches the current file. |
| A `POST /graphql` with a mutation, or a `PUT /repos/…/contents/…`, answers `Investigation connections are read-only.` | The write gate. | Expected; a fix is a pull request through the write connection. |
| The Changes page shows a repository as **last poll failed** with an HTTP status | The read token cannot see that repository, or the name is wrong. | Fix the name in **Repositories**, or the token's access. |
| A repository on a write connection shows as named but not polled | Only read-role connections are polled. | List it on the read connection. |
| Pushes arrive by webhook but merges do not | The webhook does not subscribe to **Pull requests**. | Add the event; a pull request closed without merging is never recorded. |

The generic verdict strings are explained in [Test a connection and read the failure](/help/manage-connections.md#troubleshooting). Every connection status word is in the [status and error reference](/help/status-and-error-reference.md#connection-statuses).

## Next steps {#next-steps}

- Select **Discover this connection** in the success dialog to find the services and dependencies behind it: [Launch a scan](/help/estate-harvest-and-xray.md#launch-a-scan).
- See which pushes and merges the change agent records: [Changes captured](#changes-captured).
- Let approved fixes land as pull requests: [Pull requests as fixes](#pull-requests).
- See what else each agent needs in [Choose your connection](/help/choose-your-connection.md#agent-needs).
