GitHub integration
Connecting the Aetherfy GitHub App
Aetherfy integrates with GitHub through a GitHub App. One installation of it covers every repository you grant that installation access to, and your Aetherfy account can hold several installations — your personal GitHub account, and one for each organization whose installation GitHub shows you.
afy github connectThe command prints and opens a GitHub authorization URL. Authorizing attaches every account the Aetherfy App is already installed on that you can reach; if it is installed nowhere yet, GitHub asks where to install it first, and repository selection happens on that installation screen — you choose there whether the Aetherfy App gets all repositories or a specific list. You can change that later from GitHub, and Aetherfy needs no action from you when you do, but the change is not without effect: see changing which repositories Aetherfy can see.
You can connect any organization GitHub shows you — you do not have to be an owner or an admin of it. Installing the App on an organization is what requires owner rights; connecting it afterwards does not, which is what lets the person who installs Aetherfy and the people who deploy with it be different people. Which repositories Aetherfy can reach is set by whoever installed the App, on the installation screen, not by who connects.
Anyone who can connect an organization can deploy from every repository that organization granted to Aetherfy — including repositories they cannot read on GitHub — and see their contents in build logs. The installation is the unit of access: an installation token reaches everything the installation covers, and Aetherfy does not narrow it per person. Grant the Aetherfy App only the repositories you intend to deploy, choosing Only select repositories on the installation screen rather than All repositories. You can change the selection at any time from GitHub — see changing which repositories Aetherfy can see.
The command then waits for you to finish on GitHub and reports the connection
when it lands. The link is valid for a limited time; the command tells you until
when, and stops there rather than waiting indefinitely. If it runs out before
anything is recorded, run afy github connect again for a fresh link. Pressing
Ctrl-C only stops the waiting — the link stays valid, and finishing on GitHub
still connects the account.
Running it again is how you add another account. It never replaces what you have: a personal account and an organization can both be connected, and each agent deploys through the one its repository belongs to.
Installing the Aetherfy App from GitHub
You can also install the App from its own page on github.com, without starting from Aetherfy. GitHub sends you back afterwards, but it does not tell us who you are on that path, so what happens next depends on how you sign in to Aetherfy.
If you sign in with GitHub, the installation is attached to your account automatically and there is nothing further to do. The installation event is signed by GitHub and names the account that performed it, and that is the same GitHub account your Aetherfy sign-in already proves you own.
If you sign in with email or Google, we have no way to tell which Aetherfy account the installation belongs to — an installation identifier on its own says nothing about who owns it, and attaching it on that basis would let one customer register webhooks on another customer’s repositories. The dashboard will show not connected even though the App really does have access. Run:
afy github connectGitHub recognises the existing installation and attaches it — authorizing works on installations that already exist, so you are not installing the App twice and you do not need to uninstall it first.
Checking and removing the Aetherfy GitHub connection
afy github status
afy github disconnectafy github status lists every GitHub account connected to your Aetherfy
account, with its installation id and when it was connected. Those accounts are
not always the one you sign in with, and there can be more than one: each agent
deploys through the account its repository belongs to, so the list is what tells
you which account a given agent depends on. The dashboard’s Integrations tab
shows the same list, one row per account.
afy github disconnect <account> disconnects one of them. Agents linked to that
account’s repositories stop deploying on push; agents deploying through your
other accounts are untouched.
afy github disconnect with no argument removes every connected account at once,
and is idempotent — running it when nothing is connected is not an error.
Either way, existing deployments on Aetherfy are unaffected and keep running; what stops is automatic deployment on push, whether the repository is public or private.
Uninstalling the App from GitHub does the same thing for that account, by the same route. You do not need to do both.
Your links survive it. Each agent keeps its repository, branch and directory, so
reconnecting that account resumes automatic deployment with nothing to set up
again. In the
agent list, a linked agent is marked AUTO-DEPLOY, and AUTO-DEPLOY OFF while you
are disconnected or its branch is deleted. The agent’s GitHub link panel says
which, and afy status <agent> says the same in the terminal. These are the
only places it is reported, because saying it on the commit would itself need
the GitHub access you just withdrew.
Unlinking is the separate, permanent action: use it when you want an agent to forget its repository rather than pause deployment from it.
Linking a repository to an Aetherfy agent
The agent must already exist before you can link it — linking attaches a
repository to an agent, it does not create one. The first deploy is what creates
the agent (afy deploy <path>, or afy deploy --from-github); link it after
that, and every later push deploys it.
Connecting the App is account-level. Linking is per-agent: it tells Aetherfy which repository, branch, and directory belong to a given agent.
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 |
Linking registers the webhook before it records anything, so a link either
works completely or changes nothing. If the Aetherfy App has no access to the
repository you named, the link fails with GITHUB_REPO_NOT_FOUND and any
existing link on that agent is left untouched.
That error cannot tell you which of two things went wrong, and deliberately so: GitHub hides repositories you lack access to rather than revealing that they exist. So “not found” and “not granted to Aetherfy” arrive identically. If you are sure of the name, the answer is almost always the second — grant the repository to the App and link again.
Changing which repositories Aetherfy can see
Repository access belongs to the installation, not to Aetherfy, so you change it
on GitHub: Settings → Applications → Installed GitHub Apps → Aetherfy Bot →
Configure. Add or remove repositories there and save. afy github status
prints a direct link to that page for each connected account.
Adding a repository takes effect immediately and needs nothing on the Aetherfy side. You can link an agent to it straight away.
Removing one stops that repository deploying, and says so. The webhook stays on the repository and GitHub keeps delivering pushes to it, so the next push still creates a deployment — which fails, naming the repository and telling you to either grant it to the App again or reconnect the account that owns it. Any agent still linked keeps its link throughout.
So you are not left with an agent quietly shipping code from a repository you revoked, and you are not left guessing why it stopped either. The same message appears when the account connected to Aetherfy has changed since the link was made: the two causes are indistinguishable from GitHub’s side, which is why it names both remedies.
Linking an Aetherfy agent to the wrong repository
Linking deploys nothing by itself, so a wrong link is harmless until the next push. Relink the agent to the right repository and there is nothing to undo: relinking replaces the webhook, and the old one is removed once the new link is recorded.
If a push landed first, that push built and deployed, and the agent is now running the other repository’s code. Two separate things need doing, in this order.
- Relink, or the next push repeats it.
- Roll back, to put the previous code back.
afy rollback <agent>prints the deployment history and asks which version to return to. See Rollback.
Rolling back without relinking fixes the running agent and leaves the cause in place. Relinking without rolling back stops the bleeding and leaves the wrong code running until the next real push.
Aetherfy will not deploy a repository an agent is not linked to. If a push arrives whose repository does not match the link — which happens when a link moved but its webhook did not — it is ignored rather than built. That protects against a stale webhook, not against linking the wrong repository on purpose, which is a valid instruction Aetherfy has no way to second-guess.
Choosing a branch for an Aetherfy agent
Two forms do the same thing — a flag, or an @branch suffix on the repository
argument:
afy github link my-bot myorg/my-agent --branch develop
afy github link my-bot myorg/my-agent@developIf 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:
afy github link my-bot myorg/monorepo --root-dir agents/my-botThe 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:
afy github link api-bot myorg/monorepo --root-dir agents/api-bot
afy github link report-task myorg/monorepo --root-dir agents/report-taskOmit --root-dir and the whole repository becomes the build context.
Each agent you link this way gets a webhook of its own on the repository, so one
push reaches every linked agent. Aetherfy handles each agent’s delivery
separately: a push deploys only the agents whose directory it changed, and every
other linked agent reports a skip. Each agent posts its own check on the pushed
commit, named after the agent — aetherfy/deploy/api-bot and
aetherfy/deploy/report-task for the two above — so one agent’s skip never
hides another agent’s deployment.
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:
afy github link my-bot myorg/my-agentRe-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
afy github unlink my-botThis 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:
- Clones the repository at the exact pushed commit.
- Re-parses the
aetherfy.yamlfrom that commit. - Applies it as an RFC 7396 merge patch.
- 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.
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 |
| The GitHub account this agent deploys through is not connected | No deployment created, and not announced |
| The tracked branch was deleted | No deployment created; recorded on the agent’s GitHub link |
The disconnected-account row is PER AGENT, not per Aetherfy account: each agent deploys through the GitHub account its repository belongs to, so disconnecting one account stops its agents and leaves the rest deploying.
It is also an exception to Aetherfy reporting a skip on the commit, and that is
a limit rather than a choice: posting a commit status is itself a GitHub call,
and it needs the access that being disconnected removed. A push to a linked
repository while that account is disconnected is therefore silent on GitHub. It
is reported on the agent’s page in the dashboard, and by afy status <agent>,
instead — both of which name the account to reconnect.
None of these are failures, and none of them leave a failed deployment behind. A deliberate non-deploy does not fill your dashboard with red.
The last row is the second Aetherfy cannot tell you about on GitHub, alongside
the disconnected account above it, and its reason is the sharper of the two and
worth knowing. GitHub reports a deleted branch as a push with no commit in it,
so there is no commit for Aetherfy to attach a status to — the place every other
skip gets reported simply does not exist. Deleting the branch an agent deploys
from also has a consequence the others do not: that agent will not deploy again
until the branch is back. So Aetherfy records it on the agent’s GitHub link, and
the dashboard and afy status <agent> both say the agent is linked and will not
deploy until that branch exists again — naming the branch and when it went.
Recreate the branch and push; the link is kept, so deploys resume
with nothing to set up. Over the API the same fact is branch_deleted_at on
GET /api/v1/agents/{agent}/github.
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.
A push Aetherfy accepted survives an Aetherfy restart
Aetherfy answers a push webhook the moment the delivery’s signature checks out, before the deploy has started. That acknowledgement now means Aetherfy has this push, not Aetherfy has deployed it: the delivery is written down first, and the deploy runs from that record.
What follows from it is the part worth knowing. If Aetherfy restarts — a deployment of our own, a machine replaced — between accepting your push and finishing it, the push is picked up and completed rather than lost. You do not need to push an empty commit to wake it up.
How long that takes hardly depends on how Aetherfy went away. After an orderly restart, which is the ordinary case, the work is handed back as Aetherfy shuts down and the process that replaces it collects it straight away. After a hard crash, where nothing got the chance to hand anything back, the deploy that was in flight simply stops reporting in, and another process acts on the silence. Neither case leaves a push waiting long.
A deploy that is merely slow keeps reporting in the whole time it runs, however large the repository. Only a deploy that stops reporting in for about a minute is taken back. If one is ever taken back by mistake, the late work of the original is rejected, so the push still deploys once. It does use up one of the push’s three attempts, and a push that runs out of attempts is marked failed; Redeliver gives it a fresh set.
A delivery that never reached Aetherfy at all is a different case, and GitHub shows it: the delivery is red in your repository’s webhook settings. Aetherfy looks for those too and asks GitHub to send them again, so a push made while Aetherfy was unreachable still deploys without you doing anything. If you would rather not wait, Redeliver on that delivery in GitHub’s webhook settings does the same thing immediately.
Redelivering is always safe. GitHub reuses the delivery’s identifier, and Aetherfy keys on it together with the agent the delivery was sent to — GitHub gives one push the same identifier on every agent’s webhook, so each agent keeps its own record. A push an agent has already deployed is recognised and ignored rather than deployed a second time, while a push it accepted and failed to finish is run again. That is the whole reason Redeliver is the right button after a failure — it cannot produce a duplicate deployment.
Aetherfy keeps these delivery records for a few days and no longer. They hold the push payload GitHub sent, including commit messages and committer names and addresses, and their only job is to let a push outlive a restart. Deleting an agent deletes its records with it.
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.
Each agent posts under a context named after it, aetherfy/deploy/<agent-name>.
When several agents link the same repository, the commit carries one Aetherfy
check per agent, each with that agent’s own outcome. A check keeps the name the
agent had when the push arrived, so renaming an agent in Aetherfy while it is
deploying resolves the existing check rather than opening a new one.
This makes the pushed commit the single place to look when you are unsure whether a change deployed.
A failure usually says what went wrong and what to do — the repository Aetherfy
cannot reach, the branch that no longer exists, the manifest that would not
parse. When something fails for a reason that is ours rather than yours, the
check says so plainly and carries a reference like
Reference: 11ced866-a5e9-40df-aa78-a0012ea836c1. That is the deployment’s own
id, which you can also see in the dashboard — quote it if you contact support,
and retry the push, because these failures are not caused by anything in your
repository.
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:
name: my-bot
runtime: python3.12
github_dependencies:
- myorg/[email protected]
- myorg/protocols@mainEach entry must fullmatch [A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[A-Za-z0-9._/-]+.
The @ref is a tag, a branch or a commit SHA.
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.
How Aetherfy installs a github dependency
Aetherfy installs the source archive for the ref you named, not a git
clone — https://github.com/{owner}/{repo}/archive/{ref}.tar.gz, handed to
uv pip install, npm install or bun add depending on your runtime. Private
repositories get the same URL with a build-time token that never enters an image
layer.
A dependency that builds itself on install is not built. npm runs no
prepare script for a tarball, so a JavaScript package whose published form is
compiled from its source ships only that source, and importing it fails at
runtime. Depend on such a package from a registry instead, or commit its built
output to the repository you are depending on. Python packages are unaffected:
pip and uv build a source distribution as a matter of course.
This is why the archive is used rather than git+https://: an Aetherfy runtime
image has no git binary, so a git install cannot run in one at all.