Aether runs coding agents on a headless Ubuntu server with Docker and git. Repository fetches, builds, and run tools execute there; the server needs no desktop session.
Choose where your code comes from:
| Starting point | Onboarding path |
|---|---|
| GitHub repository (public or private) | From GitHub: connect in the browser, choose a repository, and accept its revision. |
| Other public remote repository | Import public HTTPS; no local clone needed. |
| Private remote with a deploy key | Manual deploy-key import; a repository administrator must install the key. |
| Existing local clone | Create or link a workspace from the desktop app, aether gui, or CLI on the computer holding the clone. |
A workspace holds one repository, its base branch, and its runs. A run is one agent execution with its own container, git worktree, and branch. Creating a workspace or managing a remote source requires an Aether admin. Collaborators can link a clone to an existing workspace, push a local-only base, configure their own agents, and launch runs; viewers cannot.
If a server is already available, open its authenticated dashboard and start with workspace onboarding. Otherwise install and link below. A local client works on Linux, macOS, or Windows and needs git for clone linking and pushing. The hosted dashboard can import remote repositories and edit agent configuration, but cannot read a path on your computer or use its Git/SSH identity.
1. Install
On the server box and on a Linux or macOS machine:
curl -fsSL https://raw.githubusercontent.com/3xDevOps/Aether/main/scripts/install.sh | shThe script asks what this machine is. Answer server on the server box and client on your own machine; Enter takes the sensible default.
That answer decides what you get. A server gets aether and aether-server
in /usr/local/bin, with sudo. A client gets the aether CLI alone in
~/.local/bin, without sudo, so the desktop app can replace it when it
updates; if that directory is not on your PATH, the script prints the one
line that adds it, and the app finds it either way. macOS is a client
platform, so it only ever gets aether. Later, aether update upgrades
whatever is installed.
It then finishes that side's setup: step 2 on the
server, the desktop app (step 7) on a client. To
install the binaries and stop there, add --role none; see
install.md.
On Windows, download and review the PowerShell installer, then run it:
Invoke-WebRequest -UseBasicParsing -Uri https://raw.githubusercontent.com/3xDevOps/Aether/main/scripts/install.ps1 -OutFile "$env:TEMP\aether-install.ps1"
& "$env:TEMP\aether-install.ps1"It installs the CLI and desktop app by default, without administrator access.
Use -Role none for the CLI alone; that does not create a desktop shortcut.
Desktop setup requires v0.5.1-alpha.4 or newer for the Windows build and
recorded-CLI fixes; older desktop builds are refused. The desktop stays in
%LOCALAPPDATA%\Programs\Aether Desktop, separate from the CLI. Aether is
registered in the current user's Windows Programs known folder, including
folder redirection, not a guessed path under %APPDATA%. See
install.md for fresh installs, reinstalls,
script policy and version selection, and
shortcut diagnosis to
inspect the real Aether.lnk and repair it with the explicit installed CLI.
Unsigned downloads and locally built desktops can still trigger SmartScreen
or Defender; inspect the actual warning/publisher and organization policy
rather than bypassing protection.
2. Start the server
Answering server in step 1 already ran the command below on the server box. It writes the config and the systemd unit but deliberately starts nothing, so the activation line it printed is still yours to run. Run setup by hand if you skipped the question:
sudo aether-server setupIt asks for the listen address, data directory, and tailnet policy - plus, on a host that already runs tailscaled, the dashboard's HTTPS port on the tailnet and whether to use the edge (Enter accepts each default) - and, with the edge on, who may reach the server through it, a question with no default. It also offers to install Sysbox on Ubuntu and Debian, which lets agents run Docker inside their runs (environments.md). Then it prints:
systemctl daemon-reload && systemctl enable --now aether-serverOn a host without tailscaled, setup turns on the edge: a relay the Aether
project runs at https://edge.onaether.dev that the server and your machine
both dial out to, so neither needs an open port (edge.md). Setup
prints what the edge can see and aether-server config set edge-url "",
which turns it off. It then asks for the access policy: 1 (account
access) lets people you invite in by signing in with GitHub; 2
(approved devices) makes each new device wait until a person approves it.
edge.md compares them. Setup then prints the
server's id and a claim link, which makes whoever uses it first the
server's admin. The claim code inside it, <server id>-<secret>, is what
aether link --claim takes:
edge: https://edge.onaether.dev
edge access: approved-devices
server id: <server id>
edge key: pinned when the server first connects; `aether-server edge status` shows it
claim link: aether://claim?edge=https%3A%2F%2Fedge.onaether.dev&server=<server id>#<secret> (valid until 3:04PM, 5 attempts)
open it on your computer with the Aether app, or paste it into the app's Connect page; in a terminal:
aether link --claim <server id>-<secret>Run the activation line and the server is live on :2222. The SSH port is
the only thing it listens on unless you answered the dashboard question; the
edge connection is outbound. The claim code lasts 30 minutes;
sudo aether-server edge claim-code prints a new link until someone has
joined the server. Change any option
later with aether-server config set <key> <value>, then restart.
To try it in the foreground first, sudo aether-server serve runs until
Ctrl-C. install.md covers unattended installs, running
unprivileged, and every serve option.
3. Link from your machine
Through the edge, open the claim link from step 2 on your computer. The desktop app (install.md) opens at its Connect step with the link filled in: sign in with GitHub when it asks, check the account it shows, and click Claim and link. The Connect step also accepts the link pasted. In a terminal, use the claim code:
aether login
aether link --claim <code>aether login prints an address and a short code; open it, sign in with
GitHub, and confirm the code:
auth.onaether.dev signs you in for the edge edge.onaether.dev
open https://auth.onaether.dev/device and enter the code <user code>
waiting for you to confirm the code...
signed in to edge.onaether.dev as <login> (github); this device is "<host name>"
the device token in <config dir>/edge-tokens.json does not expire and is not refreshed. aether logout revokes it at
the edge and deletes it here; the edge's Devices page and deleting the account revoke it too. Your links stay:
the edge path refuses with the edge's reason, and a link's --addr still reaches the server with this device's
key until the account is deleted, which removes the account's devices on every server. SSH keys and tailnet
identities are not affected.
claiming server <server id> as <login> (github), the account edge.onaether.dev reported when this device signed in
claimed server <server id>
linked to server <server id> through edge.onaether.dev as <your name> (admin)This machine now holds its own device key; no SSH key is involved. The
claim code travels inside SSH, only to a server whose host key derives the
id in the code. The server's host key is checked against the server id on
every connection, so nothing is written to known_hosts.
By address, over a tailnet or to a reachable SSH port:
aether link <server-host>:2222linked to <server-host>:2222 as admin (admin)The first identity to link a fresh server becomes the admin, and
through the edge the claim code decides who that is. That is the whole
account setup - there is no password and no config file to edit. The link is
saved to ~/.config/aether/config.json, or %AppData%\aether\config.json
on Windows. (Joining over a tailnet, the display name comes from your
tailnet login instead of the literal admin; the role is the same. Change
any display color with aether member color <#rrggbb>.)
How you were identified by address depends on the network:
- On a tailnet: Tailscale already knows who you are and the server asks it. No SSH key, no invite code, nothing to copy. See networking.md.
- Anywhere else: your SSH public key (
~/.ssh/id_ed25519, or any key in your ssh-agent) is registered as the admin's key. With no key to offer,aether linkcreates~/.ssh/id_ed25519itself. For a key somewhere else, pass--key <path>; for a passphrase-protected one,ssh-addit first. On Windows the path is%USERPROFILE%\.ssh\id_ed25519with the OpenSSH agent service (install.md).
On first contact by address aether records the server's host key in
~/.ssh/known_hosts (%USERPROFILE%\.ssh\known_hosts on Windows) and
prints its fingerprint. Compare that against what the server printed if you
care to.
Then tell Aether who to put on your commits:
aether member git --name "Ada Lovelace" --email ada@example.comCommits Aether makes for your runs use that name and address. Without it, the
fallback is your display name at <member-id>@aether.local. The local
dashboard shows these fields at the bottom of onboarding's Connect step,
prefilled from this computer's git config; the hosted dashboard shows them at
the top of Repository, prefilled from your member record. Save identity
stores them. Change them later in Profile, from the menu under your name at
the bottom of the sidebar (Agents → Git identity links there), or inspect
them with aether member git. Author identity is not repository
authentication.
4. Create a workspace
The dashboard opens Onboarding until you finish it; later, open it from Settings → Onboarding → Open guide, which resumes at the furthest step you reached. The local dashboard lists four steps: Connect, Repository, Agent and First run, starting at Connect. The hosted dashboard omits the machine-local Connect step and starts at Repository. It opens onboarding for new members even when shared workspaces already exist.
A workspace is one repository and base branch, and the runs started from it. Repository lists your workspaces with Use. Under Add a workspace, admins can choose From GitHub, or the alternatives Import a remote repository and From a local clone. A member who is not an admin and finds no workspace sees Ask an admin to add a workspace and can still Continue to Agent. Once a workspace is chosen, Change returns to the list.
For another repository, open Settings → GitHub → Add repository; it reuses your connection. Or open Manage workspaces from Settings, the workspace selector, or command palette (Ctrl/Cmd+K), then Add workspace → Add GitHub repository. Manual remote and local choices remain available. An existing workspace's repository page (Repository in the workspace selector) links a clone and manages the source.
The base branch is the branch new runs start from. The GitHub picker uses the repository's actual default branch; manual forms need you to supply the correct branch. Creating an empty workspace does not upload code.
GitHub repository
On an already connected server, an Aether admin can do this entirely in either dashboard:
- Open Onboarding → Repository and choose Add GitHub repository under From GitHub. To reopen onboarding, use Settings → Onboarding → Open guide.
- If GitHub is not already connected, choose Connect GitHub, then Copy code and open GitHub. Enter the displayed code and approve GitHub CLI, which Aether uses to connect this server. If the clipboard or new tab is blocked, copy the visible code and use Open GitHub. Completion is automatic. Wait for Connected as …; there is no terminal or I've logged in step.
- Choose a repository. Find a repository filters the loaded list; use Load more if needed. Private repositories you can read, including collaborator and organization repositories, do not require repository-admin permission or a deploy key. Organization approval and SSO policy still apply.
- The workspace name and base branch come from the repository. When you can write to it, it is also shown as the publishing destination. Review that destination before continuing. Under Advanced, change the name or branch, choose another writable repository or fork, or select None - do not publish. Read-only sources have no publishing destination by default.
- Choose Review repository to create the workspace and fetch the branch. Review the displayed branch and actual Revision, then choose Use repository to accept that exact revision as the initial base. The repository is now ready, and onboarding continues to Agent.
Your agent vendor login and edge/server sign-in remain separate from this
GitHub connection. The server needs its normal Docker runtime and Git, and
your member Environment needs gh 2.81.0 or newer; the current standard image
includes it. See Environment prerequisites and setup.
Imported code is shared with workspace members, and future source fetches
depend on the authorizing member's connected GitHub account; see
source sharing and account dependence.
If something goes wrong: use Cancel connection to stop a pending authorization; after cancellation or expiry, Connect GitHub gives a new attempt. Use Check connection and Reload repositories after access changes. For an empty repository, add an initial commit on GitHub and reload. If a workspace was created but fetching failed, keep it: use Retry fetch, then review and Use repository. An authentication failure offers Source settings to reconnect the authorizing account; deliberately save source settings to change the bound account or branch. If an operation's result is uncertain, use Check source status, or close the dialog and inspect Manage workspaces before creating anything again.
Manual source and publishing settings
The manual import form's Advanced settings keep these separate:
- Source authentication lets the server read an upstream branch into its
source mirror. A deploy key is read-only and is not your Git/
ghlogin. - Checkout Origin is the publishing destination placed in new run checkouts. Supply a writable repository or fork, or leave it blank.
- Native Git/
ghcredentials and upstream write permission let a run push and open a PR. The admin GitHub connection configures these automatically; collaborators and CLI users can use native setup. Git author identity and agent vendor login are separate again.
Public remote repository
In Onboarding → Repository or Manage workspaces:
- Choose Import repository on the Import a remote repository card.
- Fill Workspace name, a credential-free HTTPS Source URL, and the actual Source / base branch. Set Checkout Origin (optional) separately if runs should publish upstream.
- Choose Public HTTPS under Source authentication, then Import repository. The server creates the workspace and fetches the source.
- Read Import outcome, then Continue to Repository, which opens Workspace Source. Review the observed commit and generation, choose Adopt candidate, and confirm. The first fetched candidate is not automatically accepted.
CLI alternative for a new workspace; replace the repository and branch:
aether workspace init myproject --base main
aether workspace mirror configure --workspace myproject \
--source https://github.com/acme/myproject.git --branch main --auth public
aether workspace mirror refresh --workspace myproject
aether workspace mirror status --workspace myprojectUnlike dashboard import, CLI configure does not fetch until refresh.
After reviewing the returned candidate, adopt its generation and full observed commit:
aether workspace mirror adopt --workspace myproject --generation <n> --commit <reviewed-sha> --yesUse the generation and full observed commit from the state you reviewed. If adoption reports a conflict, keep the workspace, refresh its status, and explicitly review the current candidate before adopting again; never substitute a newer SHA without review.
Private remote repository
For GitHub, prefer From GitHub above. For manual key-based
access, use the Import repository form, choosing Read-only deploy key.
Use a GitHub HTTPS source or a generic ssh:// source. For generic SSH,
Pinned known_hosts (required for generic SSH) must contain the host key
verified with the host administrator; do not blindly trust ssh-keyscan.
GitHub uses Aether's pinned host key.
- Import creates the workspace and a public deploy key, without fetching. Open Continue to Repository, then Copy public key.
- For GitHub, follow Install this key in GitHub deploy keys, or open the repository's Settings → Deploy keys → Add deploy key. Paste the key and leave Allow write access off. Other hosts need their corresponding read-only repository access setup.
- Return to Workspace Source and click Verify (or Refresh once ready). Review Observed SHA and Accepted SHA, then Adopt candidate and confirm the initial candidate.
An Aether admin role does not grant permission to install keys at the source. Your repository administrator must approve the key and any enterprise policy; the Aether server must be able to reach that Git host.
CLI alternative for a new private workspace:
aether workspace init myproject --base main
aether workspace mirror configure --workspace myproject \
--source https://github.com/acme/private.git --branch main --auth deploy-keyFor generic SSH, use this configure command instead, with a verified file:
aether workspace mirror configure --workspace myproject \
--source ssh://git@git.example.com/acme/myproject.git \
--branch main --auth deploy-key --known-hosts-file ~/.ssh/known_hostsInstall the public key printed by configure, then:
aether workspace mirror refresh --workspace myproject
aether workspace mirror status --workspace myproject
aether workspace mirror adopt --workspace myproject --generation <n> --commit <reviewed-sha> --yesReplace <n> and <reviewed-sha> with the generation and full observed commit
you reviewed from status. A conflict requires fresh status and explicit review
before another adoption. After installing a key, use Verify / Refresh,
not Save source or configure:
reconfiguration rotates the key and generation. To recover a misplaced public
key, reopen the source mirror on the workspace's repository page or run
mirror status on the same workspace.
Remote import and source recovery
Created: yes means the workspace exists even if configuration or fetch
failed. Keep its name/ID and repair it on the workspace's repository page;
do not import a duplicate. If the response was lost, inspect Manage
workspaces or aether workspace list before trying creation again.
Switching members or servers, or closing the import dialog, does not cancel
a pending import on the original server. Its late result cannot update the
new dashboard context; return to the original server and check its workspaces
before importing again.
For a source branch named main, failures include:
| Error | Recovery |
|---|---|
gitengine: mirror auth-failed for branch "main": upstream authentication failed; check source access and the mirror deploy key |
Install or restore the current public key at the source, then Verify. |
gitengine: mirror source-missing for branch "main": upstream branch was not found; verify the configured branch |
Check the repository and branch. Restore the branch or deliberately correct the source configuration; reconfiguration rotates a deploy key. |
gitengine: mirror auth-failed for branch "main": host key verification failed; verify the mirror's pinned host key |
Verify the host identity with its administrator. Do not disable host checking. Restore the trusted endpoint or deliberately reconfigure the verified pin and install the newly generated deploy key. |
A failed authenticated fetch or host-pin check reports the failure while retaining known accepted/observed commit metadata. That is not a successful refresh: new launches do not silently use the retained base. Restoring source access and refreshing the unchanged configuration keeps the same workspace, key, and generation.
After initial adoption, forward-only source updates advance the mirrored base. Rewrites or divergence require another reviewed Adopt candidate. Every launch refreshes a configured source. If a refusal reports an accepted commit, an explicit one-request override is available:
aether run "add a health check endpoint" --workspace myproject --agent claude \
--cached-base <accepted-commit>The SHA must still match the accepted commit and unmoved workspace base. Later launches do not inherit the override. Disable source returns the workspace to local-only mode; it does not revoke the key at the Git host. See workspace source operations and the gateway API.
Local clone
On the computer holding the clone, open the desktop app or run aether gui
after linking to the server.
- In Onboarding → Repository or Manage workspaces, choose Create from local clone on the From a local clone card. Enter Workspace name and the clone's existing Base branch, then Create workspace.
- Under Local clone, enter the absolute Repository path, or use the desktop app's Choose folder, then Add remote. For a workspace you picked with Use, choose Link local repository first.
- Check the connected path and destination, then Push now. What git did retains Git's output. A fresh workspace receives the base branch; an existing one reports whether it is current, ahead, or diverged.
- If offered, Fast-forward my clone catches up without a merge commit. Divergence shows commands to resolve it yourself; the dashboard never force-pushes. Continue to the Agent step after the base is available.
For an existing workspace, open its repository page (Repository in the workspace selector) and choose Link local repository. Use Use a different repository to relink. Each local server profile keeps one current clone, not one per workspace; check the displayed workspace and path before pushing after a switch. Relinking leaves the previous clone and its history in place.
The hosted dashboard shows these choices with a local-client/CLI handoff; it cannot link a path on your laptop. A collaborator can link an existing workspace and push its base when source status confirms it is local-only. Creating a workspace or configuring, refreshing, adopting, or disabling a mirror still requires an admin. All admitted members can inspect source status.
If source ownership cannot be checked, linking remains available but base-push controls and commands are withheld. Resolve the displayed error before pushing; a configured mirror's base remains server-owned.
CLI equivalent below assumes the clone's intended base is trunk. Replace
the path, workspace, and trunk with your actual values. Replace
<server-address-or-id> with the server's SSH address, including its SSH port,
or its server ID, not the hosted dashboard's HTTP address:
server='<server-address-or-id>'
git -C "$HOME/code/myproject" branch --list &&
aether link "$server" &&
aether workspace add myproject --base trunk &&
aether link "$server" --workspace myproject --repo "$HOME/code/myproject" &&
git -C "$HOME/code/myproject" push --no-follow-tags aether trunk:trunkFor an existing workspace, omit workspace add and use the base branch
shown in its settings. link requires the server argument even if the client
is already linked. With --repo, it adds or updates the clone's aether
remote; it does not change its origin.
The push is non-force and does not send tags. Do not push a mirrored base:
linking a clone there is for pulling run branches; use the source mirror on
the repository page to refresh or adopt the server-owned base.
On the first eligible link, Aether records the clone's origin as the
workspace's checkout Origin if none is set, printing workspace origin -> <url>. A later link does not overwrite it. GitHub SSH URLs are normalized to
HTTPS. Inspect or change it deliberately for new run checkouts:
aether workspace origin --workspace myproject
aether workspace origin --workspace myproject https://github.com/my-account/myproject.gitNeither linking nor recording Origin supplies upstream credentials. See workspaces for local push conflicts and source policy, and member environments for installing tools and saving the image new containers use.
Setup script and variables
A workspace can prepare every run that starts in it. Its setup script runs in each new run's container before the agent does, in the run's checkout. Its variables are set in that container; one marked secret is never shown again once saved. Only an admin can change either.
printf 'npm ci\n' > setup.sh
aether workspace env script --workspace myproject --file setup.sh
aether workspace env set --workspace myproject NODE_ENV=test
aether workspace env set --workspace myproject --secret NPM_TOKEN < token.txt
aether workspace env import --workspace myproject .envset --secret reads the value from stdin, and import stores the values of
a .env file as secrets. The dashboard has the same settings under
Environment on the workspace's Repository page. Runs launched afterwards
get them; a container that already exists does not. If the script fails, the
launch fails and shows what the script printed.
environments.md covers how the
script runs and who can read a secret.
5. Set up your agent
An agent is the coding CLI a run starts. Onboarding → Agent opens with two cards comparing Standard and Enhanced, the two ways a run can show an agent; on the Agents page, What's the difference? shows the same cards. Both list each agent with Installed or Not installed, Login found or No login found, and the modes it supports (Standard · Enhanced, or Standard), and one button: Set up, Log in or Run. Once one agent is installed, the Agents page folds the others under More agents. Setup belongs to your member account, not to one workspace. Set up opens three numbered steps:
Choose how runs show it. Two cards draw the same moment of one run:
- Standard - the agent's own terminal, exactly as on your machine. It acts without asking; anything it asks is answered in the terminal.
- Enhanced - Aether reads what the agent is doing: messages, tool activity, file changes, approvals and progress appear as native controls. It runs through an adapter, misses some agent-specific commands and screens, and starts a few seconds slower. Like Standard, it acts without asking by default; for Claude Code and Codex, switch its mode in the run to be asked. The card's note says how the chosen agent runs without asking (enhanced-runs.md).
Below the cards, one line each says how the agent supports Enhanced, how it is installed, whether a running agent can switch, what happens when the adapter fails, and for Claude Code which login pays. An agent without Enhanced shows that card disabled with the reason. The choice starts on Enhanced for Codex, oh-my-pi, OpenCode, Copilot, Gemini and Cursor and on Standard for the others, and becomes the agent's default in New run.
Install and log in. Install <agent> runs the vendor's install command in your environment, the container Aether keeps for you on the server, followed by the pinned adapter when Enhanced is chosen. A failed command shows Install failed with the exit status and the command's output. Once the agent is installed, your Environment's terminal opens with its login command typed:
claudethen/login,codex login,pithen/login,omp,opencode auth login,copilot login --device-code,NO_BROWSER=true gemini,NO_OPEN_BROWSER=1 agent login, oragy.Check reads
agent.listagain and shows Installed, Enhanced installed, and Login found or No login found. Aether only looks for the agent's login file; the agent itself proves the login when it starts. Done returns to the list, where Run starts the agent. For Codex, Check also shows Trust Aether's hooks once: your first Standard run opens Codex's Hooks need review screen, where you choose Trust all and continue. Codex remembers it in~/.codex/config.tomland asks again only if Aether's hooks change.aether agent add codexprints the same note.
From the CLI:
aether agent add claude --enhanced
aether terminalAdd agent… registers a CLI Aether does not ship: a Name, a
Standard command whose first word is the executable, a Background
command, and an optional Enhanced command that serves the Agent Client
Protocol. Install that executable into ~/.local/bin from the terminal in its
setup.
Your environment's home persists the executable and vendor login state across containers. Environment in the header (or More navigation) opens the same shell; terminal.md covers its tabs and Stop environment.
Once one agent is ready, the dashboard's Assistant button (or Ctrl+Shift+Space)
opens a chat that can list your runs, launch one, or install tools in your
environment. See assistant.md.
No vendor login yet? Prove the plumbing without an agent subscription runs the whole path with a scripted stand-in.
Upload agent configuration
An agent's configuration - settings, instruction files such as CLAUDE.md,
commands, subagents, skills, hooks, MCP and plugin configuration - lives in a
directory such as ~/.claude. Runs read the same path in your home, the
persistent directory Aether keeps for you on the server. Uploading
configuration is separate from installing an agent or logging in.
A coding agent on the computer that has the configuration decides what to
upload, asks you, and uploads it with the aether config CLI:
- Open Agents > Agent config files (or Onboarding > Agent) and click Copy prompt.
- Paste the prompt into Claude Code, Codex, opencode, or another agent on
that computer. It needs the
aetherCLI linked to the server (step 3). - Approve or change its plan: what it will upload, which server files it would replace, what it leaves behind, and the commands it will run on the server afterwards.
The prompt:
Move my coding-agent configuration from this computer to my Aether server, so agents I run there behave the way they do here. The server runs Linux. Use the `aether` CLI, which is linked to it.
1. Run `aether config help`. If `config` is an unknown command or the help does not list `config cat`, run `aether update`, which prints the path of the binary it updated. If the help still lacks it, a different `aether` comes first in PATH; update that one.
2. Run `aether config ls`. It lists each agent Aether can hold configuration for, its directory (the same path here and on the server, such as ~/.claude), whether the agent is installed on the server, and what Aether always leaves out. Run `aether terminal exec 'echo $HOME; uname -sm'` for the server's home directory and platform.
3. For each listed directory that exists here, look through it and decide what belongs on the server:
- Upload: settings, instruction files such as CLAUDE.md or AGENTS.md, custom commands, subagents, skills, hooks, and MCP or plugin configuration.
- Leave behind: logins, tokens, and API keys; session history, transcripts, caches, and logs; anything that only works on this computer, such as hooks or MCP servers that run programs at local absolute paths, or macOS-only tools.
4. Run `aether config push --dry-run <agent> <path>...` on what you chose. Each path is a file or directory inside the agent's directory, relative to it; a directory takes everything in it. The dry run uploads nothing and says what a push would do with each file: `would create` it, `would skip` it because the server's copy is identical, `would refuse` it because the server has a different file, or `would leave out` a file Aether never uploads. Read the server's copy of every refused file with `aether config cat <agent> <path>` and compare it with mine.
5. Show me the plan and wait for my approval. List separately: new files; files that would replace a different server file, with what differs and whether you recommend replacing it; what you leave behind; and the commands you will run on the server afterwards. Give a one-line reason for each. When a file mixes settings that belong on the server with ones that only work here, write an edited copy to a temporary file and show me the change. Do not edit my local files.
6. Upload what I approved with `aether config push <agent> <path>...`. A push never replaces a different server file without `--overwrite`; pass it only in a push that names exactly the replacements I approved. Upload an edited copy with `aether config push <agent> <path> --from <temporary file>`.
7. Finish on the server with `aether terminal exec '<command>'`, which runs one command in the Environment where my agents are installed: for example, reinstall a plugin or MCP server whose configuration you uploaded, then check that it loads.
8. Report what was created, replaced, skipped, and left out, with Aether's reason for each file it left out. End with what I still need to do myself and its exact command, such as logging in to an agent, which needs `aether terminal`.
Never print, copy, or upload a credential.The agent runs these commands; you can run them yourself:
aether config ls
aether config ls -r claude
aether config cat claude settings.json
aether config push --dry-run claude settings.json CLAUDE.md skills commands
aether config push claude CLAUDE.md skills commands
aether config push --overwrite claude settings.json
aether config push claude settings.json --from /tmp/settings.server.jsonaether config ls prints one entry per agent: its name for the other
commands, its directory (the same home-relative path on this computer and on
the server), whether that directory exists on this computer, whether the
agent is installed on the server, and what is always left out:
claude (Claude Code): ~/.claude, exists on this computer, installed on the server
always left out, credentials: .credentials.json, credentials.json, credentials, .claude.json, auth.json, keychain, token.json, tokens.json, oauth.json, agent.db, agent.db-wal, agent.db-shm, *.pem, .env, .env.*
always left out, runtime and history: projects/, shell-snapshots/, statsig/, todos/, file-history/, history.jsonl, daemon/, transcripts/, sessions/, session-env/, cache/, backups/, plugins/cache/, plugins/marketplaces/, plugins/synced/, skills/synced/
codex (Codex): ~/.codex, does not exist on this computer, not installed on the server
...
always left out, every agent: symlinks, special files, files over 64 MiB, .aether-profile-ignore, and files the secret scanner flagsAn agent you registered is listed when its definition sets ProfileRoot
(custom agents).
aether config ls <agent> [path] lists what the server has in that
directory, in the aether files ls format: dir<TAB><name> and
file<TAB><size><TAB><name>. With -r it also lists every directory below,
with names relative to the one you asked for. aether config cat <agent> <path> prints one of the server's text files. Neither shows a path that is
always left out.
aether config push <agent> <path>... uploads files and directories. A
relative path resolves against the agent's directory, not the current one; an
absolute path such as ~/.claude/skills must be inside it; . uploads the
whole directory. Directories are walked recursively. Before reading a file,
the push leaves out:
- credential names in any path component, matched case-insensitively, and
every
*.pem,.env, and.env.* .aether-profile-ignore- the agent's runtime and history paths, matched case-sensitively from the
directory root, where
*stands for any run of characters in one name - symlinks, which are not followed, and sockets, FIFOs, and devices; a path
you name that runs through a symlink is left out whole, as in
left out skills/linked/SKILL.md: symlink skills/linked, not followed - files over 64 MiB (67,108,864 bytes); install or download those in
aether terminal
It then runs the server's secret scanner on each remaining file and leaves out every flagged file, so its bytes never leave your computer. There is no override. What is left is compared with the server's copy:
- a file the server does not have is created
- a file that is identical on the server is skipped
- a file the server has with different content is refused, and the server's
file stays;
--overwritereplaces it instead - a file whose path on the server is a folder, a symlink, or a hard link, or runs through one, is left out
The server applies the same rules, so a dry run lists exactly what a push does. Each file gets one line:
left out .credentials.json: credential
left out history.jsonl: runtime or history path
left out projects/: runtime or history path
left out skills/linked: symlink, not followed
created CLAUDE.md
skipped commands/review.md: identical on the server
refused settings.json: the server has a different file (1108 bytes, 40769 here); --overwrite replaces it
created skills/deploy/SKILL.md
left out skills/deploy/notes.md: secret detected at 0:27 (generic-api-key)
created 2, replaced 0, skipped 1 identical, refused 1, left out 5 in ~/.claude (42 bytes uploaded)A push that refused a file still uploads the rest, then exits non-zero. Read
the server's file with aether config cat, then push that path again with
--overwrite, which prints replaced settings.json. --overwrite applies
to every path in the command, so name only the files you mean to replace.
--dry-run uploads nothing and prints the same lines as would create,
would skip, would refuse (would replace with --overwrite), and
would leave out, then
would create 2, replace 0, skip 1 identical, refuse 1, leave out 5 in ~/.claude (42 bytes to upload).
--from <file> uploads that file as the one <path> you name; the path need
not exist on this computer, and your own file at that path is not read. Use
it for a copy you edited for the server, such as a settings.json without
the hooks that only work locally. Every rule above applies to it.
A push deletes nothing. A new file gets the local permission bits, so
scripts stay executable; a replaced file keeps its server mode. From Windows,
new files are 0644. Files travel in requests of at most 2,000 files and
about 20 MiB; the directory itself has no size or file-count limit. If the
server fails partway through a request, the push prints created <path> or
replaced <path> for what it wrote and exits with the server's error. If a
request gets no answer, the push prints unconfirmed <path> for each file in
it and exits with the error; aether config ls -r <agent> shows what the
server has. Running the same push again is safe. The command exits 0 when
the server confirmed every request and nothing was refused, even if files
were left out.
An upload writes your home at once: your Environment and active and future runs see it, and a running agent may need to reload. Nothing watches the local directory; push again after changing it. Edit uploaded files in Files (harnesses.md).
For OMP, root-level stats.db, stats.db-wal, and stats.db-shm are
runtime exclusions; settings, MCP configuration, skills, and extensions are
not. See the exact OMP exclusions.
OpenCode: its configuration directory is ~/.config/opencode, not
~/.local/share/opencode. Files an older Aether release imported under
~/.local/share/opencode are not moved; upload the configuration again with
aether config push opencode <path>..., then reload OpenCode. Leave the login
at ~/.local/share/opencode/auth.json.
Agent coordination hooks
Agent messages are what agents in different runs send each other. They
are durable: a successful send means stored, not read. An agent actively
awaiting a reply should use aether-internal inbox --wait 30 and explicitly
acknowledge the batch only after handling it.
Managed Claude Code launches and loaded native pi, OMP, and version-matched OpenCode integrations can wake an eligible live idle session without terminal keystrokes. Native wake respects human protection, takeover, and Stop. Manually installed Claude Code hooks and the Codex, Copilot CLI, Gemini CLI, and Cursor CLI command hooks instead announce new messages at their next supported lifecycle boundary. Managed Standard Codex launches load their hooks automatically, but Codex runs them only after you trust them once. Neither path restarts an exited run.
Inside a run, aether-internal skill --hooks checks configuration and prints
setup instructions. Configured is not loaded, trusted, or executed:
restart or reload as the agent requires and inspect its real hook/plugin
errors.
See per-agent setup for managed
loading, copyable files, versions, and disable controls; see
delivery and acknowledgement
for the runtime contract. An unlisted CLI can follow
harness-integration.md without adding a new daemon
or terminal fallback.
Connect GitHub
If runs should publish to GitHub, connect an account with write permission to the checkout Origin from step 4. Source deploy keys do not grant that permission, and enterprise policy may require additional authorization.
Admins who connected under From GitHub are already set up: Onboarding → Agent recognizes that connection. To connect later, use Settings → GitHub → Connect GitHub, or the admin GitHub controls in the Agent step. Check connection only reads status; it does not configure Git or signing.
Collaborators retain their own native setup: in Onboarding → Agent or Agents, open GitHub, then Connect GitHub. This member flow opens the Environment terminal with the login command typed; I've logged in finishes setup and reports the account and registered signing key.
From the CLI it is two commands. In aether terminal:
gh auth login --hostname github.com --git-protocol https --web \
--scopes admin:ssh_signing_keyThen, back on your machine:
aether github connectWhat each step writes, how to re-run it, and how to revoke are in environment-home.md and security.md.
6. Launch a run
The dashboard's First run step is the New run form below, with the same rules: task, agent and mode. It names the workspace and base branch and checks that the server can read the base. If no agent is installed it says No agent is installed yet and offers Set up an agent. For an empty or unaccepted base, use Review repository, finish the push or source adoption, then Check again. After Launch the run starts its container, pulls the image the first time the server uses it, then starts the agent (about 5 s more in Enhanced). Installation does not prove vendor login or upstream publishing permission.
aether run "add a health check endpoint" --workspace myproject --agent claudeThe run gets its own container and checkout while using your persistent home.
In the dashboard, New run asks for the task, the agent and the Mode:
Standard opens the agent's own terminal, Enhanced shows its
messages, approvals and progress as native controls
(enhanced-runs.md), and Background runs the task once
with no interaction. The CLI takes --mode enhanced or --mode background.
--model <model> picks the agent's model; omit it for the agent's default.
run run-d6ay38tj8j runningEvery ID Aether generates names its kind, then ten random characters, such
as run- for a run, ws- workspace, mem- member, mis- swarm, task-,
att- worker attempt, sub- submission, ques- swarm question, msg-
agent message, ack- inbox delivery token, rpt- agent report. Servers
upgraded from earlier versions keep their older 26-character IDs, and both
forms are accepted.
aether runs lists your visible runs. Scoped commands can omit --workspace
only when exactly one workspace exists; keep it explicit when adding projects.
To hand one objective to a team of agents instead, see
Launching a swarm.
7. Watch it
aether guiThis serves the dashboard from your own machine and opens a browser tab
already carrying a per-process token. It rides your SSH key, so everything
the CLI can do works from the page, plus local actions like pulling a run
branch into your clone. Leave it running; Ctrl-C stops the gateway and the
token dies with it. aether gui --url prints the URL instead of opening a
browser. See local-gateway.md.
On a tailnet, the server can host the dashboard instead, so a phone or any
other tailnet device opens https://<the server's MagicDNS name>/ with
nothing installed and no token. Set web-port and restart the server; see
networking.md. On a phone the sidebar folds
away: the top bar holds Open sidebar (with a dot when a run needs you),
the current page's name, Search and New run. To hear about that run
with the dashboard closed, turn on Settings > Notify this device when a run
needs me (networking.md). Hosted onboarding supports remote
repository import, Git identity, agent setup, the configuration prompt, and
editing configuration in Files.
It has no machine-local link, clone, push, or pull operations; those require
the desktop app or aether gui on the computer holding the clone.
Prefer a native window?
aether gui in a browser tab is the whole dashboard. If you would rather it
lived in its own window - with desktop notifications and a dock badge when a
run needs you, plus aether://run/<id> deep links - build
the desktop app. Answering client in step 1 already did this. Nothing has
to be installed first - the CLI fetches its own Node.js copy when the machine
has none, which makes the first build longer:
aether gui buildThat installs Aether into your application menu (Linux), your Applications
folder (macOS; ~/Applications without administrator rights, and the command
prints the path), or the Start Menu (Windows). Open it like any other app.
Two things to know:
- There is no download. No release publishes an installer.
aether gui buildpackages the Electron shell on your machine from sources carried in the CLI. Details in install.md. - It is not a standalone client. The app does not bundle
aether; it launchesaether guifrom yourPATH, and the dashboard lives inside that CLI binary. Install the CLI (step 1); on an unlinked machine, the app opens its local onboarding wizard and links from there. When you update the CLI, the window picks up the new dashboard without rebuilding the app.
In the dashboard, the sidebar holds the workspace switcher, New run, your
runs, and the pages: Board, Swarms, Activity, Files,
Environment, Agents and Templates. The Board groups runs into
Needs you, Working and Finished. A run opens on Session
(Enhanced) or Terminal (Standard), with Changes, Files and
Browser beside them, and a Details panel holding Needs you and
Notes. Watching is
read-only by default; typing into a run needs the steer capability, which as
the owner you have.
Browser is a real Chromium that the server runs beside the run, so
localhost there is the run's own app. It lists the ports the run is
listening on: click localhost:5173 to open it, or type any address and
press Enter. Nothing has to be forwarded to your machine. A localhost link
printed in the run's terminal opens there too. The agent can drive the same
page, and the toolbar says so when it does.
To use your own browser instead, click Open on this computer beside the
port. It forwards the port and opens http://localhost:5173 on your machine.
That needs aether gui or the desktop app; the dashboard your server hosts
shows the command to run instead:
aether forward <run-id> 5173A forward reaches a server listening on 0.0.0.0, not one bound to
127.0.0.1; the Browser view reaches both.
Files is the run's checkout: a tree and an editor, where Save writes
your edit into the checkout the agent works in. A file's path in Changes
opens it there. So does a path the agent names in Session or prints in
the terminal, such as web/src/app.ts:42, at that line: point at the path
and click it once it is underlined. Only a path that is a file in the
checkout becomes a link. The Files page keeps the base branch and
Home, every file in your home on the server: there you create, rename,
move, upload, download and delete from each row's … menu
(environment-home.md).
The terminal escape hatch is aether attach <run-id> - a raw byte-for-byte
passthrough where every native keybind and theme of the agent's own TUI works.
Detach without killing anything: the PTY lives on the server.
To message a running agent without attaching:
aether message <run-id> "also update the README"The message appears in the run's Session under your name, so everyone watching sees who sent it.
Open a run in your editor
A live run is a machine your own editor attaches to over SSH. Run this once on your computer:
aether ssh-configIt writes a Host *.aether block to ~/.ssh/aether_config and adds one
Include line for it at the top of ~/.ssh/config; nothing else in your
config changes. From then on every run is an ssh host named after its id:
ssh <run-id>.aether # a login shell in the run's checkout
ssh <run-id>.aether git status # one command, with its exit status
scp notes.md <run-id>.aether: # into the checkout; sftp works the same way
ssh -L 3000:localhost:3000 <run-id>.aether # a port on the run's own loopbackIn the dashboard, a live run's More menu has Open in editor…, with
VS Code, Cursor and Zed buttons that open the checkout at
/workspace. In the editor itself, connect to the SSH host
<run-id>.aether. aether ssh <run-id> [command] works without the setup
step.
You can connect to a run you could open a shell in: your own, or a teammate's when you may control it and they have shared their agent account with you. Any other attempt is refused with the server's reason. The editor downloads its remote server into the run's home directory on first connection. An attached editor trusts its remote, and this remote is a container an agent controls, so attach only to runs you would run on your own computer; see security.md. terminal.md has the details.
8. Review and publish the result
Comment on the diff
Open the run's Changes view. Point at a line and click the + beside
its number, write what should change and click Comment. Drag across the
numbers, or Shift-click a second line, to comment on a range. Comments wait
in a bar under the toolbar until Send to agent delivers all of them as one
message, each with its path:line, the quoted diff lines and your text. They
stay in this browser tab until you send them.
Remote-only: commit, push and open a PR
Open the run's Changes view and click Publish…. The dialog runs
native Git and gh in the run's checkout as the run's account. Land,
candidate review and the interval menu stay in the Changes view: a GitHub PR
is not an internal candidate proposal.
- Read the line at the top: the checkout's branch, its commit, the agent account and the GitHub login, or that login's real authentication error. Connect GitHub in that account's environment first; mirror deploy keys do not provide push credentials.
- With nothing left to commit, the dialog opens on step 2: skip to step 4. Otherwise check exact paths, then click Review selected paths. The view shows worktree and staged diffs separately, with content previews for selected untracked files. Enter a Commit message and click Commit selected. This commits those paths' current worktree contents, not only their staged hunks; unselected staged paths are preserved. Native identity and signing apply, but commit hooks do not run.
- Inspect Commit outcome and native diagnostics. Committed: yes with Index updated: no means the commit exists but index reconciliation failed: inspect the checkout instead of repeating the commit. A branch/HEAD mismatch requires Refresh status and a fresh review. Then click Continue to push.
- 2 · Push and pull request says where the work goes in two sentences: the commit, the push URL and the branch, then the pull request's target and the GitHub login it is opened as. The push goes to the branch's upstream or the workspace's checkout Origin. The pull request goes from that repository and branch into the run's base branch of the repository the workspace's source came from, which for a fork Origin is the upstream. Neither choice reconfigures the mirror or Origin.
- Check PR title, which holds the last commit's subject, add a PR
description, optionally check Draft PR, and click Push and open
pull request. It pushes, without force and without switching branches,
then opens the pull request. If the branch already has a pull request,
including one the agent created using native
gh, the dialog shows it and the button is Push branch. - Read the outcomes under the sentences. Pushed: yes stays whatever the pull request did; if the pull request failed, its real error is under the second sentence and the button becomes Open pull request. If creation is uncertain, use Reconcile PR read-only rather than repeating it.
To publish somewhere else, open Change destination: the push remote, its URL and branch, and the pull request's repository, base and head. It opens by itself when nothing could be chosen, for example in a workspace without a checkout Origin, or when a destination refused. After changing a pull request field, click Discover existing PR to look that exact target up. Push only pushes without opening a pull request.
The server refuses a push or a creation when the branch, HEAD, push URL or
GitHub login is no longer the one on screen, and says what it found instead.
No automatic merge, force push, branch switch or mutation retry is performed.
GitHub discovery, creation and feedback need a working native gh, network
access, and the run's GitHub login's permissions on the explicit upstream/fork:
the gh login in the home the run's container mounts: its launcher's, also on
a shared agent account and after a handoff.
Review and merge on GitHub according to your repository's policy.
Follow the pull request from the run
Once the run's branch has a pull request, whoever opened it, the run page
shows it at the right of the view switch: #412 opens it on GitHub, and
beside it is the state of its checks, such as 2 failing or Checks
passed. Aether reads this from GitHub every minute while the run is live
and its page is visible.
Click the check state for the checks and review comments. A failing check or a comment has Send to agent, which sends the agent one message with the check's name, conclusion and link, or the comment with its file and lines. It is a normal moderated message: the row says whether it was sent or queued, and Open the session shows it in the conversation.
Optional: pull into a local clone
When the TUI agent exits, the run stays alive in a login shell. Start another installed agent in the same checkout if needed; exiting that shell opens another login shell, so the run remains live until you explicitly close it. Close commits and publishes the latest work to the run's branch.
aether pull <run-id>If your checkout is already on the run branch, Aether fast-forwards it. If not, Aether creates or updates the local run branch without switching your checkout:
Branch aether/run-add-a-health-check-endpoint-d6ay38tj8j is ready. Switch with: git switch aether/run-add-a-health-check-endpoint-d6ay38tj8jThe branch name is aether/run-<slug>-<short-id>: the task slugified, then the
random part of the run ID (the last six characters of an older 26-character
ID). Aether never switches branches or merges
the run into your base branch. These examples use trunk, as in the local
clone setup above; substitute your workspace's actual base branch. Review
and diff the run branch:
git log --oneline aether/run-add-a-health-check-endpoint-d6ay38tj8j
git diff trunk...aether/run-add-a-health-check-endpoint-d6ay38tj8jIf the local checkout has uncommitted changes, the pull still fetches the run branch and reports that the checkout is dirty. Commit or stash those changes before switching to the run branch or merging it.
When the review is complete, merge locally. In a local-only workspace, push the reviewed base back to Aether:
git switch trunk
git merge aether/run-add-a-health-check-endpoint-d6ay38tj8j
git push --no-follow-tags aether trunk:trunkIn a mirrored workspace, the Aether base is protected. Push the reviewed result to the configured source branch using credentials for that upstream:
git push <source-remote> trunk:<configured-source-branch>Here <source-remote> is a local remote (or URL) for the configured source;
the mirror's server-side credentials are read-only. The checkout Origin is
optional and independent of the source mirror. It may be a different
repository, and is a review destination for publishing the run branch (for
example, to open a pull request); it is not the destination for a mirrored
base update.
An agent's completion report leaves a Standard or Enhanced run open. Send a follow-up in the message box or type into the Standard agent's terminal; neither requires reopening. Reporting does not start an expiry timer.
When finished with the session, close it:
aether close <run-id> --outcome merged # or --outcome abandonedClosing retains the exact Standard or Enhanced container, checkout, run row,
member account and coordination surfaces for --run-container-ttl (default
7 days). Before retention expires, reopen the same run with Reopen run in its More
menu, or:
aether reopen <run-id>Reopening does not create a new run or container, and expired or unavailable runs cannot be reopened. Kill run and Delete run remain immediate cleanup; Free container… removes the retained container early, after which the run cannot be reopened.
Swarm workers also retain their exact containers for the same default 7 days after a success/failure report or container exit, without continuing work. Inspect their Session, Changes, Captures and worker details normally. Unlike explicitly closed ordinary Standard or Enhanced runs, completed workers cannot be reopened.
The local daemon is optional. It fetches server-owned run branches as agents
commit and can push your local base branch in local-only workspaces. It
does not watch agent configuration directories; configuration is imported
explicitly in either dashboard and edited in Files. It
does not fetch a mirror source or forward a checkout's origin into the
workspace. In a mirrored workspace, a base push attempt is rejected because
the mirror owns that branch; use aether workspace mirror refresh and, when
needed, explicit aether workspace mirror adopt instead. aether pull remains
available when you prefer reviewing in a local clone.
aether daemon install --server <server-host>:2222 --repo ~/code/myproject
systemctl --user daemon-reload && systemctl --user enable --now aether-daemonThat second line is the Linux one. daemon install prints the activation
command for whatever platform you are on: launchctl load on macOS,
schtasks /Create on Windows. A unit installed by an older release that still
contains --sync-origin keeps restarting after upgrade;
reinstall it with the command above and activate the newly written unit. The
reinstalled unit shows failed in systemctl --user status aether-daemon
when its arguments are bad. There is no recurring
dashboard Sync from origin action.
Prove the plumbing without an agent subscription
No vendor login yet? Aether ships a deterministic agent named fake for
exactly this: it runs a script from your repo instead of an agent, so you can
drive the whole lifecycle end to end and see a real branch come back.
Start the server with the fake agent's command in its environment. If the
systemd unit from step 2 is already running, stop it first
(sudo systemctl stop aether-server) - two servers cannot share :2222:
AETHER_FAKE_AGENT="sh /workspace/agent.sh" \
aether-server serve --data-dir /var/lib/aether --addr :2222/workspace is where the run's checkout is mounted, so agent.sh is just a
file in your repo. Create a throwaway one:
mkdir demo && cd demo
git init -b main
git config user.name "You" && git config user.email you@example.com # if git has no identity yet
cat > agent.sh <<'EOF'
echo "agent starting"
printf 'hello from the agent\n' > result.txt
echo "agent done"
EOF
echo "# demo" > README.md
git add -A && git commit -m seedThen run steps 3, 4, 6 and 8 above with the default standard image and
--agent fake instead of --agent claude. Skip step 5: fake has no agent
login. Step 7 (aether gui) works too if you want to watch.
Launching it is the CLI's job, though. fake is a server-side registration
rather than an executable installed in your account, and the local dashboard's
two launch surfaces - the launch form and the wizard's First run step -
offer only agents installed in the account, plus the custom agent a
deployment pins, so fake never appears in either.
aether link <server-host>:2222
aether workspace init demo --base main
aether link <server-host>:2222 --workspace demo --repo "$PWD"
git push --no-follow-tags aether main:main
aether run "write a result file" --workspace demo --agent fake
aether runs
aether pull <run-id>aether runs prints ID, STATUS, AGENT, MEMBER, ACCOUNT, OVERLAP,
TITLE and TASK columns; within seconds the run's STATUS reads Working
and AGENT reads fake. The script exits into the run's shell, which stays
open until you close the run, so after --stall-threshold with no output
STATUS reads Waiting. A success the agent reported reads
Ready for review until you mark it complete or later; a reported failure
reads Failed. The
pulled branch carries a commit adding result.txt. That is the full path -
container, worktree, PTY, commit, fetch - with nothing mocked but the agent.
When something does not work
| Symptom | Cause |
|---|---|
not linked; run aether link <addr> |
CLI commands that need a server have no saved link. The desktop app opens its onboarding wizard and can link from there. |
no Aether member for this key |
The server already has an admin, so you are not the first member. Get an invite: teams.md. |
unable to authenticate, attempted methods [none] |
The CLI had no key to offer: none at ~/.ssh/id_ed25519 and no ssh-agent. The same error names the key when one was found but could not be used - read the rest of the line. On Windows, check Get-Service ssh-agent and look for the key at %USERPROFILE%\.ssh\id_ed25519. |
<path> is passphrase-protected; add it to ssh-agent (ssh-add <path>) or pass --key <unencrypted key> |
The key exists but the CLI cannot decrypt it; it does not prompt for a passphrase. Run ssh-add <path>, or point at an unencrypted key with aether link <addr> --key <path>. |
parse ssh key <path> |
The file at that path is not an SSH private key (a public key, or a truncated file). Pass the private key with aether link <addr> --key <path>. |
link --key: stat <path> / ssh key <path>: open <path>: no such file |
The key path you chose is not there. A chosen key is never skipped in favor of the agent, so re-link with the right --key. Re-linking without --key keeps the saved one; to go back to ~/.ssh/id_ed25519, delete the key line from ~/.config/aether/config.json. |
host key mismatch / REMOTE HOST IDENTIFICATION HAS CHANGED on aether link |
The server was reinstalled and generated a new host key, but your known_hosts still trusts the old one. Clear it: ssh-keygen -R '[<server-host>]:2222'. |
not signed in from aether link --claim, aether link <join link>, aether edge connect or aether servers |
This machine has no edge sign-in. Run aether login, with --edge <url> for an edge other than the default. |
claim code is wrong, claim code expired or claim code has no attempts left |
On the server, sudo aether-server edge claim-code prints a new claim link, valid 30 minutes. Once the server has members it needs --admin <member id>; an admin connects from the dashboard instead (edge.md). |
the server has members, so a claim without --admin would be refused; ... |
Someone already joined, by SSH key or over the tailnet. That admin runs Connect my GitHub account in Settings › Remote access, or aether edge connect from a linked terminal. |
server is not connected to the edge |
The edge holds no control connection from the server: it is stopped or cannot reach the edge. On the server, sudo aether-server edge status shows the last connection error, or stale when the server stopped or hung without recording one. |
not a member of this server |
Your edge account has no membership or open invitation there, or the invitation was revoked or expired. An admin runs aether invite --github <your-login> and sends you the new link (teams.md). |
<account> is invited to this server; open the invitation link the admin sent, or run: aether link <link> |
You linked by server id. Open the link the admin sent, or run aether link '<invite link>'. |
the invitation link is not valid for this server: wrong, revoked or expired; ask the admin for a new one |
The link's secret does not match an open invitation. Ask the admin for a new link. |
device "<label>", signed in as <account>, is waiting for approval |
The server admits approved devices only (edge-access approved-devices), and this one is new. An admin clicks Approve on its Needs you card in the dashboard, or you do from a device you already use. When the message carries an approval code instead, nobody can approve it in the app: run one of the commands it prints, such as sudo aether-server device approve <code> on the server. |
10 devices of <account> are waiting for approval on this server already, so no new one is recorded |
An admin approves or revokes the waiting devices with aether device list and aether device revoke <device-id>, or sudo aether-server device review on the server. |
this device connects as "github:<user id>", but the edge signed the connection in as <account> ...; nothing was recorded |
The edge vouched for another account than the one this device signed in as. Nothing changed on the server; tell the edge's operator. |
... was admitted by signing in alone and is waiting for approval: this server now admits approved devices only |
The server switched from account to approved-devices. Approve it the same way. |
tailnet identity unavailable; key authentication required |
Informational, not an error. The server has Tailscale but this connection did not arrive over the tailnet, so it fell back to your SSH key. |
membership pending admin approval |
You joined over a tailnet on a server that requires approval. An admin runs aether member approve <your-member-id>. |
no workspace yet; skip git remote |
Run aether workspace add myproject --base main, then aether link '<server-address-or-id>' --repo /absolute/path/to/clone --workspace myproject with your server and clone path. |
multiple workspaces available; specify --workspace |
Pass --workspace <name> to aether link or another command that accepts a workspace selector. Agent setup is member-scoped. |
Run reaches failed immediately |
The agent started and exited. aether timeline --run <run-id> shows the exit code; aether attach only works while a run is alive. |
self-update is not supported on Windows |
Expected. Re-download the release binary: install.md. |
Starting over
Testing the whole path from a clean slate, or handing the box to someone else?
install.md has the full removal order for the
server, the client, and your linked repos. Two things bite people: run
containers outlive the server unit and must be removed separately, and a
reinstalled server gets a new host key, so stale known_hosts entries have to
go or the next aether link fails.
Next
- install.md - systemd, upgrades, data layout
- environments.md - member images, saving, resetting, and persistence
- environment-home.md - your environment's home, installed agents, and migration
- networking.md - Tailscale-first, plus LAN and VPN
- teams.md - joining, roles, workspaces
- harnesses.md - agents: login, configuration, and launch definitions
- security.md - what the container boundary does and does not do