# GitHub

**The GitHub integration gives hlix a place to push task branches and open pull requests.** It is a GitHub App installed on your organization or account, granting hlix access to the repositories you select — nothing more.

Without it, a project still runs: task branches are merged inside the project's own [Coding Workspace](/concepts/coding-workspace/) and the result is downloadable. GitHub adds the external review surface your team already uses.

## Prerequisites

- Agency `owner` or `admin` in the hlix workspace
- Permission to install a GitHub App on the target organization or account. Without it, GitHub records your install as a **request** and an organization owner must approve it.
- A repository you want hlix to work in

## Connect it

1. **Start the install.** From the dashboard's integrations screen, or directly:

   ```bash
   curl -sS -i "$HLIX_BASE_URL/v1/api/integrations/github/install" \
     -H "x-api-key: $HLIX_API_KEY" \
     -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
   ```

   Expected result: a `302` to `github.com`, carrying an HMAC-signed state that binds the install to your workspace and user. The redirect is the whole point — do not follow it with a script.

2. **Choose the account and the repositories.** GitHub asks whether to grant **all** repositories or a **selected** list. Prefer selected; you can add more later without reinstalling.

3. **Land back on hlix.** The callback exchanges the installation ID, reads the installation's account, repository selection, permissions, and subscribed events, and persists one row for this workspace.

   Expected result: a redirect to `…/integrations?installed=1&provider=github`. If an organization owner still has to approve, the flag is `pending=1` instead, and the row exists but the installation is not usable yet.

4. **Verify.**

   ```bash
   curl -sS "$HLIX_BASE_URL/v1/api/integrations/github" \
     -H "x-api-key: $HLIX_API_KEY" \
     -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
   ```

   Then list what it can actually see:

   ```bash
   curl -sS "$HLIX_BASE_URL/v1/api/integrations/github/$INSTALL_ID/repositories" \
     -H "x-api-key: $HLIX_API_KEY" \
     -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
   ```

   Expected result: the repositories you granted. An empty list with a healthy installation means the selection did not include what you expected — change it on GitHub, not in hlix.

## What hlix writes back

Per task, against the repository the project is connected to:

| Write | When |
| --- | --- |
| A task branch off the project's base branch | when the task's worktree is created; an existing branch is adopted rather than duplicated |
| Commits on that branch | as the coding agent works; the driver force-pushes the task branch |
| A pull request | on the cycle's delivery path, targeting the base branch |
| A merge of a task branch into the base | on the merge path, when the project is configured for it |

Branch creation is idempotent: a lost race that returns GitHub's `422 Reference already exists` is treated as success, because the branch being there is all the caller wanted.

hlix does not rewrite your history, force-push your default branch, or touch a branch it did not create.

## Permissions and scopes

The installation's granted permissions and subscribed events are read from GitHub at callback time and stored with the row, so what hlix believes it can do always comes from GitHub rather than from a hard-coded list. Inspect them on the installation record, or on GitHub's installation settings page.

Two consequences worth knowing:

- **Repository selection is GitHub's, not hlix's.** Adding a repository is a change you make on GitHub; hlix picks it up through the `installation_repositories` webhook.
- **Suspension is honoured.** A suspended installation answers `409 installation suspended on github` rather than failing deep inside a task.
**Webhook signatures are mandatory:** `/webhooks/github` verifies every payload's `x-hub-signature-256` against the configured secret, in constant time, before parsing anything. A deployment with no secret configured **refuses the endpoint entirely** (`500`) rather than accepting unverified installation state.

## Disconnect

```bash
curl -sS -X POST "$HLIX_BASE_URL/v1/api/integrations/github/$INSTALL_ID/revoke" \
  -H "x-api-key: $HLIX_API_KEY" \
  -H "X-Organization-Id: $HLIX_WORKSPACE_ID"
```

`owner` or `admin` only. This marks the hlix-side row revoked. Uninstalling the app on GitHub is the other half, and the one that actually removes hlix's access — do both.

## If GitHub does not connect

- `500` — ``GitHub App not configured``. The deployment has no GitHub App credentials. Operator fix.
- Redirect with `error=github_missing_installation_id` or `github_missing_state` — the callback was reached without GitHub's parameters. Start the flow again from `/install` rather than opening the callback URL directly.
- Redirect with `error=github_invalid_state_…` — the signed state failed verification or expired. Restart the install.
- Redirect with `error=github_installation_lookup_failed` — hlix could not read the installation back from GitHub. Usually a revoked or suspended app; check GitHub's installation page.
- `pending=1` after install — an organization owner must approve the request. Nothing works until they do.
- `409` — ``installation suspended on github``. Unsuspend it on GitHub.
- `502` — ``failed to list repositories``. GitHub refused the listing; the installation may have been uninstalled since the row was written.
- `Branch "main" not found in owner/repo. Check that the branch exists and the GitHub token has access.` — the project's base branch does not exist in that repository, or was not granted.
- A webhook arriving before the callback persisted its row is **acked, not failed** — the callback is the canonical writer, and the webhook retries state changes only.

## Next steps

[Reviewing output](/running/review/)
  [The Coding Workspace](/concepts/coding-workspace/)
  [Linear](/integrations/linear/)
  [Integrations](/integrations/)