> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runaether.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Teleport

> Move a live Claude Code or Codex session from your machine to an Aether task

Teleport hands a coding session in progress off to Aether. You are working locally in Claude Code or Codex, you need to close the laptop, and instead of writing a handoff prompt you run one skill: your working tree is pushed to a fresh branch, the conversation transcript is imported, and the [task](/guides/tasks) that opens on Aether resumes that exact conversation with full memory of what you were doing.

Want to keep this session local and give a separate piece of work to a cloud agent? Use [cloud delegation](/guides/cloud-delegation). Teleport moves the current conversation and working state; delegation starts a separate task from pushed code and brings its result back to your local conversation.

Two things travel:

* **The repo's working state.** Every tracked and untracked change (respecting `.gitignore`) is committed to a fresh `aether/teleport-<hex>` branch and pushed to `origin`, along with any local commits `origin` does not already have. The commit is built through a temporary index, so your local index and working tree are left untouched. Linked worktrees (`git worktree add`) capture the same way as a primary checkout.
* **The session transcript.** The local CLI's session file is uploaded and seeded as the task's resume checkpoint, so the agent continues the conversation rather than starting a new one.

The model comes across too, and so does the thinking level. Both are part of the session's state, so the script reads the values the session was last using out of that transcript — including ones you switched to mid-session — and the Aether task resumes on them. The project's default model, agent profile, and default reasoning effort are not consulted: continuing a conversation on a different model, or at a different depth, than it was held at is a silent swap, not a default. `--model` and `--reasoning-effort` override.

Teleport creates the new task at the `medium` VM size: the endpoint supplies that required size explicitly. The skill does not expose a size flag and does not read a hardware default from the project.

The two differ in one way. A model is required, so a transcript with none is an error you fix with `--model`. A thinking level is optional: sessions that never recorded one — older sessions, and models with no thinking tiers — teleport with the field absent and run at the server's default.

They travel together, though. The level is read within the newest model's stretch of the conversation — walking back from the latest turn for the last level actually set, and stopping where the model changes. Not every line records a level, and one that doesn't is silent rather than a reset, so a level you set stays in force. But it does not survive a model change: a session that switched to a model without thinking tiers teleports at the new model with no level, rather than pairing the new model with the old one's — a combination the server rejects. `--model` onto a *different* model is a model change like any other and drops the transcript's level for the same reason; `--reasoning-effort` sets it explicitly when you want one anyway.

## Prerequisites

| Requirement | Detail |
| - | - |
| Runtime | Claude Code or Codex. Other agent runtimes cannot teleport. |
| Python | `python3` 3.9 or newer. The script is standard library only. |
| Repo | The repo's `origin` must be a GitHub repo [connected to an Aether project](/guides/github-integration). |
| Credential | A [platform API key](/api-reference/authentication) exported as `AETHER_TOKEN`, or a logged-in `aether` CLI. |

## Installing the Skill

The skill is two files — a `SKILL.md` and the `teleport.py` script it runs. Install the public bundle with the Aether CLI; this command does not require a login:

```bash theme={"dark"}
aether skill install aether-teleport
```

The only supported install name is `aether-teleport`. By default the command installs into `~/.claude/skills/aether-teleport` when it finds Claude Code and into `~/.agents/skills/aether-teleport` when it finds Codex. Both may be installed. To skip detection and install into one exact directory, use `--dir`:

```bash theme={"dark"}
aether skill install aether-teleport --dir /path/to/agent/skills/aether-teleport
```

Seeing `~/.codex` is what selects the Codex target, but the bundle goes to `~/.agents/skills` rather than beside it, because [Codex scans](https://developers.openai.com/codex/skills.md) the repo's `.agents/skills` chain, `$HOME/.agents/skills`, and `/etc/codex/skills` — never `~/.codex/skills`. Only Claude Code and Codex are detected, matching the prerequisite above: other agents read the shared directory, but teleport itself cannot run under them. The installer skips whichever runtime is absent and fails if both are.

### No-CLI fallback

If the Aether CLI is not installed, use the shell fallback:

```bash theme={"dark"}
curl -fsSL https://app.runaether.dev/skills/aether-teleport/install.sh | sh
```

The shell fallback uses the same detected targets; set `AGENT_DIR` to install into exactly one directory instead. Both installers download both files before either is put in place, so a failed or interrupted download leaves an existing installation exactly as it was rather than truncating it or half-updating it.

### Manual per-agent fallback

Or fetch the two files yourself:

<Tabs>
  <Tab title="Claude Code">
    ```bash theme={"dark"}
    mkdir -p ~/.claude/skills/aether-teleport && \
      curl -fsSL https://app.runaether.dev/skills/aether-teleport/SKILL.md -o ~/.claude/skills/aether-teleport/SKILL.md && \
      curl -fsSL https://app.runaether.dev/skills/aether-teleport/teleport.py -o ~/.claude/skills/aether-teleport/teleport.py
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={"dark"}
    mkdir -p ~/.agents/skills/aether-teleport && \
      curl -fsSL https://app.runaether.dev/skills/aether-teleport/SKILL.md -o ~/.agents/skills/aether-teleport/SKILL.md && \
      curl -fsSL https://app.runaether.dev/skills/aether-teleport/teleport.py -o ~/.agents/skills/aether-teleport/teleport.py
    ```
  </Tab>
</Tabs>

<Note>
  This is a local skill for the CLI on your machine, not an [Aether skill](/guides/skills) uploaded to your account. It has to live where the local agent can load it, because it reads that agent's own session files.
</Note>

## Authenticating

Teleport calls the Aether API as you. It looks for a credential in two places, in order: `AETHER_TOKEN` in the environment, then the stored key of a logged-in `aether` CLI (`aether auth token`). If neither produces one it stops and names both remedies — it never proceeds unauthenticated.

Both sources now yield a platform API key, so neither expires mid-run. Prefer `AETHER_TOKEN` when the agent's environment may not inherit your login, or when you want a credential you can revoke independently of the CLI. Create one under **Settings → API Keys**, or from the CLI:

```bash theme={"dark"}
aether token create --name "teleport"
```

Export it wherever your shell will have it when the agent runs:

```bash theme={"dark"}
export AETHER_TOKEN="aether_..."
```

If you would rather rely on the CLI, `aether auth login` is enough — teleport will ask the CLI for its key when `AETHER_TOKEN` is unset. That key is valid until revoked, so the fallback keeps working for as long as the machine stays logged in.

<Warning>
  A platform API key is unscoped and does not expire — it can do anything your account can. Treat it like a password: keep it out of the repo, store it in your shell profile or a secret manager, and revoke it with `aether token revoke <id>` if it leaks.
</Warning>

## Teleporting a Session

From the session you want to move, invoke the skill — `/aether-teleport`, or just tell the agent to teleport this session to Aether.

<Warning>
  **Teleporting publishes more than your working tree.** The capture is `git add -A`, so anything `.gitignore` does not exclude is committed — a tracked or merely un-ignored `.env`, private key, or `.npmrc` included. The push then puts that commit *and every local commit `origin` does not already have* on the remote. A secret in an unpushed commit therefore teleports from a perfectly clean working tree, and so does one a later commit deleted, because the blob stays reachable through history.

  As a backstop the script scans exactly that set — every file added or modified by the commits the push would newly publish — and aborts, listing them, if any basename looks like a credential (`.env`, `.env.*`, `*.pem`, `*.key`, `id_rsa*`, `id_ed25519*`, `.netrc`, `.npmrc`, `*credentials*`, `service-account*.json`, `*.p12`, `*.pfx`). Files `origin` already has are not reported: teleporting does not make them any more public. To decide that accurately the script runs `git fetch --prune origin` first, because a branch deleted or rewound on the server leaves a stale local tracking ref that would otherwise make an unpublished commit look published. If `origin` cannot be reached the script fails rather than scanning against stale refs.

  The scan matches on **filename only**, so a secret in a file it does not recognize still teleports. Once you have confirmed the flagged files are safe to publish, `--allow-secrets` proceeds.
</Warning>

If the scan flags a file, the fix depends on where it lives:

* **Only in your working tree.** Add it to `.gitignore`, and `git rm --cached <file>` if it is already tracked.
* **Already in an unpushed commit.** `git rm --cached` is not enough here — it records a *later* deletion while the earlier blob stays reachable, so the push would still publish it and the guard still refuses. Rewrite every commit that contains the file (`git rebase -i`, or a soft reset and recommit), then re-run.
* **Ever pushed anywhere.** Assume the credential is burned and rotate it. Scrubbing it from history does not un-leak what was already published.

<Steps>
  <Step title="The agent prints a nonce">
    It emits a `teleport-nonce: <token>` line into the conversation. That line is how the script identifies which transcript file on disk belongs to this session, so it has to be written **before** the script runs.
  </Step>

  <Step title="The script captures and pushes your work">
    It commits the working state and pushes it to a new `aether/teleport-<hex>` branch on `origin`. The base branch is your current branch if it has an upstream, otherwise `origin/HEAD`.
  </Step>

  <Step title="The transcript is imported">
    The session file is compressed and posted to the teleport endpoint, which validates it and creates the task with the conversation pre-seeded.
  </Step>

  <Step title="You get a task URL">
    The script prints the Aether task URL. Open it and the agent picks up where the local session left off.
  </Step>
</Steps>

Once the script succeeds, stop working locally. Edits made after the capture never reach the teleported task.

### Options

The agent passes `--agent` and `--nonce` itself. The rest you can ask it to add:

| Flag | When you need it |
| - | - |
| `--message` | A one-sentence handoff describing the current state and the next step. Defaults to a generic resume instruction. |
| `--model` | Overrides the model read from the transcript — use it to move the session onto a different model, or when the one it was using is not available on Aether. Also required in the rare case where the transcript carries no model at all (a Codex rollout with no turn-context line, for instance); the script fails loudly rather than guessing one. |
| `--reasoning-effort` | Overrides the thinking level read from the transcript. Pass an empty value (`--reasoning-effort ""`) to send none at all — the escape hatch for a session whose level the target model does not accept. Omit the flag and the session's own level travels. |
| `--base` | Sets the base branch explicitly. Needed when the current branch has no upstream and `origin/HEAD` is unset. |
| `--allow-secrets` | Pushes files the secret scan flagged. Only pass it after reading the list the script printed and confirming those files are safe to publish. |
| `--dry-run` | Runs the local checks and prints the payload without pushing or creating the task. |

#### What a dry run actually checks

A dry run is a client-side rehearsal, not a preflight against the Aether API. It confirms that a credential resolves, that the nonce locates a session transcript and that the file is self-consistent, that the gzip checkpoint artifact is at most 32 MiB, and that the provider-specific raw limit is respected (32 MiB for Claude Code, 320 MiB for Codex). It also confirms that a model can be read from the transcript, that your `origin` matches an Aether project, and that no file the push would newly publish trips the secret scan. It reaches the network twice — listing your projects, and running `git fetch --prune origin` for the secret scan — plus once more if it falls through to the CLI for a token. Nothing you own changes: your working tree, index, and branches are untouched, no branch is pushed, and no task is created. Two pieces of local Git bookkeeping do update, as they would on any fetch: `origin/*` remote-tracking refs are refreshed, and the capture commit's objects are written to the local object store (unreferenced, so `git gc` reclaims them).

It does **not** validate the transcript's contents — malformed JSON on a later line passes the client — and it never contacts the teleport endpoint, so server-side transcript validation, model availability, and credential checks are untested. The thinking level is read but never checked: whether the model accepts it is the server's call, so a dry run prints a `reasoning_effort` the real run can still reject. A clean dry run can still be followed by a real run that pushes a branch and is then rejected.

## What Does Not Teleport

The conversation and the code come across. The rest of your local machine does not:

* **Gitignored files, and history `origin` already has.** A `.env` that is gitignored and was never committed stays behind — and the workspace uses the project's configured [environment variables](/guides/environment-variables) and [secret files](/guides/secret-files) instead, so add anything the session depended on to the project first. A `.env` that is *not* ignored, or that sits in an unpushed commit, teleports; see the warning above.
* **Running processes.** Dev servers, watchers, and background jobs you started locally are not carried over.
* **Locally configured MCP servers.** The workspace has its own tool surface.

<Note>
  Your local CLI may be a newer version than the one pinned in the workspace. Transcripts are read tolerantly across versions, but a structurally invalid transcript is rejected at import, and a resume that would silently start a fresh session fails the task instead of continuing without memory. Teleport never quietly drops your history.
</Note>

## Troubleshooting

The script prints every failure to stderr prefixed with `teleport:` and exits non-zero. It never falls back to creating an ordinary task.

### Recovering from a failed run

The branch is pushed **before** the task is created, so a failure is not automatically a clean slate:

* A failure after the push leaves an `aether/teleport-<hex>` branch on `origin`.
* If the response is lost in flight, the script reports failure even though the server created the task. Retrying then produces a second branch and a second task.

Before you retry, check your [task list](https://app.runaether.dev) for a task on that branch. If one is there, the teleport worked — open it. If not, retry; each run generates a fresh branch name, so retries never collide. Either way, delete branches you have abandoned:

```bash theme={"dark"}
git push origin --delete aether/teleport-<hex>
```

| Error | Cause and fix |
| - | - |
| `no Aether credential` | Neither source produced a token: no `AETHER_TOKEN` in the environment the agent runs in, and no usable CLI login. Create a key (**Settings → API Keys** or `aether token create`) and export it as `AETHER_TOKEN`, or run `aether auth login`. The message reports what the CLI said, which distinguishes "not installed" from "not logged in". |
| `no session file contains the nonce` | The nonce line never reached the transcript, or the script ran from a different directory than the one the session started in. The script waits 10 seconds for a late flush, so this is not a timing fluke: ask the agent to print a fresh nonce in an *earlier* message than the one that runs the script, and run from the session's directory. |
| `no Aether project matches <owner/repo>` | The repo's `origin` is not linked to any of your projects. [Import the repo](/guides/github-integration) into a project first. The message lists the repos that do match. |
| `HTTP 400: unsupported reasoning_effort ...` | The thinking level read from the transcript is not one Aether's catalog accepts for that model, which happens when your local CLI offers a level Aether does not. The message names the value, the agent, and the model. Re-run with `--reasoning-effort <supported>`, or `--reasoning-effort ""` to send none. `HTTP 400: model "..." does not support reasoning_effort` is the same situation for a model that accepts no level at all, where only the empty form works. |
| `could not determine this session's model` | No model was found in the transcript — no assistant line carrying `message.model` for Claude Code, no `turn_context` record carrying `payload.model` for Codex. Lines Claude Code generated itself, which record `<synthetic>` instead of a model, do not count. The script will not guess one from the project. Pass `--model <id>`. |
| `pushing this session would publish these files` | The push would newly publish files whose names look like credentials; the message lists them. The remedy depends on whether each file is uncommitted, already in an unpushed commit, or previously pushed — see [the fix list above](#teleporting-a-session). Re-run with `--allow-secrets` if they are safe to publish. |
| `HTTP 409: a task already exists for branch ...` | A task in that project already claims the head branch. Re-run — each teleport generates a new random branch name. |
| `session transcript is N raw bytes; the <agent> raw limit is ...` | The transcript exceeds its provider's raw safety bound: 32 MiB for Claude Code or 320 MiB for Codex. Start a fresh session with a summary of the work and teleport that. |
| `session transcript is N bytes gzipped; the checkpoint artifact limit is 33554432` | The gzip artifact exceeds the global 32 MiB checkpoint cap, even if the raw transcript is within its provider's limit. Start a fresh session with a summary of the work and teleport that. |
| `could not determine a base branch` | The current branch has no upstream and `origin/HEAD` is unset. Pass `--base <branch>`. |

## API

Teleport is a thin client over one endpoint — [Teleport a session into a task](/api-reference/v1/tasks/post-tasks-teleport). It takes the project, base and head branches, the agent and model, the thinking level when the session has one, and the gzipped session transcript, and returns the created task.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.