---
slug: agents/github
title: GitHub integration
kind: howto
surface: agents
summary: How to connect the Aetherfy GitHub App, link a repository and branch to an agent, use --root-dir to keep several agents in one repository, and what happens on each push including the cases Aetherfy deliberately ignores.
sources:
  - aetherfy-cli:cmd/github.go
  - aetherfy-control-plane:shared/github_service.py
  - aetherfy-control-plane:shared/github_webhook_processor.py
  - aetherfy-control-plane:api/routes/github.py
  - dashboard/pages/api/controlplane/github.js
---

# GitHub integration

## Connecting the Aetherfy GitHub App

Aetherfy integrates with GitHub through a **GitHub App**, not an OAuth app. You
install it once, and that single installation covers every repository you grant
it access to.

```bash
afy github connect
```

The command prints and opens the installation URL. Repository selection happens
on GitHub's own installation screen — you choose there whether the Aetherfy App
gets all repositories or a specific list, and you can change that later from
GitHub without touching Aetherfy.

## Checking and removing the Aetherfy GitHub connection

```bash
afy github status
afy github disconnect
```

`afy github status` reports the current connection state.

`afy github disconnect` removes the stored installation and is idempotent —
running it when nothing is connected is not an error. Existing deployments on
Aetherfy are unaffected and keep running; what stops is automatic deployment on
push.

## Linking a repository to an Aetherfy agent

Connecting the App is account-level. Linking is per-agent: it tells Aetherfy
which repository, branch, and directory belong to a given agent.

```bash
afy github link my-bot myorg/my-agent
```

| Flag | Short | Default | Purpose |
|---|---|---|---|
| `--branch` | `-b` | `main` | The branch Aetherfy tracks for deployments |
| `--root-dir` | — | repository root | The repo-relative folder holding this agent's code and `aetherfy.yaml` |

## Choosing a branch for an Aetherfy agent

Two forms do the same thing — a flag, or an `@branch` suffix on the repository
argument:

```bash
afy github link my-bot myorg/my-agent --branch develop
afy github link my-bot myorg/my-agent@develop
```

If you give both and they disagree, `--branch` wins.

Aetherfy tracks exactly one branch per agent. A push to any other branch does not
deploy.

## Keeping several Aetherfy agents in one repository

`--root-dir` names the repo-relative folder that holds one agent's code and its
`aetherfy.yaml`:

```bash
afy github link my-bot myorg/monorepo --root-dir agents/my-bot
```

The flag does two things at once on Aetherfy, and both matter:

| Role | Effect |
|---|---|
| Config search root | Aetherfy looks for `aetherfy.yaml` inside that subtree, not at the repository root |
| Build context | **Only that subtree is uploaded.** Sibling folders never enter the build. |

Because the build context is scoped, this is how several agents live in one
repository without each one dragging in the others' code:

```bash
afy github link api-bot   myorg/monorepo --root-dir agents/api-bot
afy github link report-task myorg/monorepo --root-dir agents/report-task
```

Omit `--root-dir` and the whole repository becomes the build context.

## The webhook secret Aetherfy shows once

The response to `afy github link` prints a **webhook secret exactly once**. It
is not retrievable later — Aetherfy will not show it again, and there is no
command that reveals it.

Copy it when it appears. If you lose it, or you want to rotate it, re-run
`afy github link` on the already-linked agent:

```bash
afy github link my-bot myorg/my-agent
```

Re-linking an already-linked agent is supported and is the intended rotation
path. Aetherfy creates the new webhook **before** removing the old one, so
automatic deployment keeps working throughout the rotation with no window where
pushes are dropped.

## Unlinking a repository from an Aetherfy agent

```bash
afy github unlink my-bot
```

This removes the link and is idempotent. The webhook is deleted on a best-effort
basis, so an unlink still succeeds if GitHub cannot be reached at that moment.

## What a push to Aetherfy does

A push to the tracked branch that touches the agent's `root_dir` triggers a
deployment. Aetherfy then:

1. Clones the repository at the **exact pushed commit**.
2. Re-parses the `aetherfy.yaml` from that commit.
3. Applies it as an RFC 7396 merge patch.
4. Builds and deploys.

Step 3 is the one to understand. Because the configuration is a merge patch,
fields you omit from the pushed file keep the values you set through the
Aetherfy dashboard or API — a push does not reset your agent to the file's
defaults. The full merge-patch table is on
[/agents/aetherfy-yaml](/agents/aetherfy-yaml).

Broken YAML fails the deployment loudly. Aetherfy never silently falls back to a
previous configuration.

## When Aetherfy ignores a push

Aetherfy creates no deployment at all in these cases:

| Case | Result |
|---|---|
| The event is not a push | Ignored |
| The branch does not match the tracked branch | Ignored |
| The repository does not match the linked repository | Ignored |
| The agent is paused | Announced on the commit, no deployment created |

Path filtering behaves differently, and deliberately so: **it fails open.** If
Aetherfy cannot prove that a push missed your `root_dir`, it deploys anyway.
That happens with force-pushes, branch creations and deletions, and very large
commit lists where the changed-file set is not fully available.

The consequence is worth stating plainly: an occasional deployment you did not
expect is the designed behaviour. Aetherfy prefers a redundant deploy over a
silently skipped one, since a redundant deploy is visible and a missed deploy is
not.

## Commit statuses Aetherfy posts back

Aetherfy posts commit statuses on the pushed commit — `pending` first, then
`success` or `failure`. Deliberate skips are reported too, so a push that
Aetherfy chose not to deploy says so on the commit rather than leaving you to
guess from silence.

This makes the pushed commit the single place to look when you are unsure
whether a change deployed.

## Private repositories in github_dependencies on Aetherfy

The `github_dependencies` field in `aetherfy.yaml` lists repositories to pull in
at build time, each as `owner/repo@ref`:

```yaml
name: my-bot
runtime: python3.12
github_dependencies:
  - myorg/shared-lib@v1.2.0
  - myorg/protocols@main
```

Each entry must fullmatch `[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[A-Za-z0-9._/-]+`.

Private repositories listed here require the Aetherfy GitHub App to be connected
and to have access to them. Run `afy github connect` first, and make sure the
dependency repositories are among those you granted on GitHub's installation
screen.
