# Running ox in constrained environments (/docs/cli/ephemeral-mode)



Running ox in constrained environments [#running-ox-in-constrained-environments]

"Ephemeral" is not a mode. It's a label for a **constellation of capabilities the sandbox provides or withholds.** Claude Code Cloud, Devin, GitHub Actions, and Codespaces are not all the same shape — one has persistent disk and an 8-hour lifetime, one writes disk to a snapshot but can run a daemon, one is gone in five minutes. `ox` probes each capability once at startup and lets every subsystem ask for what it actually needs.

This page covers the capability model, what works and what doesn't, how to set `ox` up on each major platform, and the exact error strings runbooks should grep for.

The capability model [#the-capability-model]

When `ox` starts, it probes the environment for four capabilities and a lifetime hint:

| Capability        | Question                                                      | Set by                                               |
| ----------------- | ------------------------------------------------------------- | ---------------------------------------------------- |
| `PersistDisk`     | Will writes to `~/.sageox/` survive the next `ox` invocation? | filesystem probe + `OX_PERSIST_DISK` override        |
| `LongLivedHelper` | Can a background helper outlive a single CLI invocation?      | lifetime hint + `OX_NO_DAEMON` override              |
| `Browser`         | Can `ox` open an interactive URL for device-code auth?        | TTY + `DISPLAY`/`BROWSER` + known-headless detection |
| `Network`         | Is egress open, allowlisted, proxied, or offline?             | platform-derived; `OX_NETWORK` override              |

Subsystems consult capabilities directly. The local CodeDB indexer only runs when `PersistDisk && LongLivedHelper`. `ox login`'s browser flow only attempts to launch when `Browser` is true; otherwise it refuses with a clear "set `SAGEOX_TOKEN`" message. Session staging picks `~/.sageox/sessions/...` when `PersistDisk` is true, otherwise `$TMPDIR/ox-sessions/...`.

Capability matrix per platform [#capability-matrix-per-platform]

| Platform              | PersistDisk | LongLivedHelper | Browser | Network   | Lifetime   |
| --------------------- | ----------- | --------------- | ------- | --------- | ---------- |
| Local laptop          | yes         | yes             | yes     | open      | persistent |
| GitHub Codespaces     | yes         | yes             | no      | open      | hours      |
| Claude Code Cloud     | no          | yes             | no      | allowlist | hours      |
| Devin                 | no          | yes             | no      | open      | hours      |
| GitHub Actions runner | no          | no              | no      | open      | minutes    |

<Callout type="info">
  **Codespaces is no longer treated as ephemeral.** A Codespace has persistent disk and a multi-hour lifetime — it behaves like a slow laptop. The previous `CODESPACES=true` auto-detection forced the constrained path and threw away the persistent disk Codespaces paid for. First-run does the setup, second-run reuses it.
</Callout>

<Callout type="warning">
  **`--ephemeral` is deprecated and will be removed.** It only sets the env var inside one `ox` process — POSIX prevents writing back to the calling shell, so subsequent `ox` commands lose the signal and silently degrade. The canonical pattern is to export `OX_EPHEMERAL=1` into the session env (e.g. write it to `$CLAUDE_ENV_FILE` before running `ox`), which is what every recipe on this page already does. The flag is redundant at best and a footgun at worst. The CLI accepts it for one release with a stderr deprecation warning, then removes it.
</Callout>

Override knobs [#override-knobs]

You can override the probe at the capability level:

| Variable               | Effect                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `OX_PERSIST_DISK=0`    | Force the in-memory / `$TMPDIR` paths even when `~/.sageox/` is writable                    |
| `OX_NO_DAEMON=1`       | Disable any background helper; run everything inline in the CLI process                     |
| `OX_NETWORK=allowlist` | Tell `ox` it is running behind an egress allowlist (skip probes that would spuriously fail) |
| `OX_EPHEMERAL=1`       | Coarse shortcut: sets `PersistDisk=false, LongLivedHelper=false`                            |

Use the capability-level overrides when the auto-probe gets it wrong (e.g. a brand-new sandbox `ox` doesn't recognize). Use `OX_EPHEMERAL=1` when you just want "treat me as fully constrained" without spelling out each axis.

Prerequisites [#prerequisites]

* A [Personal Access Token](/docs/cli/pats) exported as `SAGEOX_TOKEN`.
* A static `ox` binary on `PATH`. The install script at `https://install.sageox.ai/ox` ships a single self-contained Go binary with no runtime dependencies.

What works, what doesn't [#what-works-what-doesnt]

Subcommand behavior in constrained environments. "Designed-for" subcommands are the supported entrypoints; "degraded" still works but with a noted trade-off; "refused" exits non-zero with a clear pointer.

| Subcommand                    | `PersistDisk=false`                        | `LongLivedHelper=false`     | `Browser=false`                              |
| ----------------------------- | ------------------------------------------ | --------------------------- | -------------------------------------------- |
| `ox agent prime`              | designed-for                               | designed-for                | designed-for                                 |
| `ox distill --since=<window>` | designed-for                               | designed-for                | designed-for                                 |
| `ox status`                   | designed-for                               | designed-for                | designed-for                                 |
| `ox doctor`                   | designed-for                               | designed-for                | designed-for                                 |
| `ox query "..."`              | works (refetches each call)                | works                       | designed-for                                 |
| `ox code search`              | degraded (remote-only; slower)             | degraded (remote-only)      | designed-for                                 |
| `ox import <video>`           | works (stages to `$TMPDIR`, syncs at exit) | works (sync upload at exit) | designed-for                                 |
| `ox kb` writes                | degraded (warns; writes lost at exit)      | works                       | designed-for                                 |
| `ox login`                    | works                                      | works                       | **refused** — exit non-zero, pointer to PATs |
| `ox daemon`                   | **refused**                                | **refused**                 | works                                        |

Session uploads are idempotent on `(agent_id, session_id)`. Retrying a failed upload is safe — the second call is a no-op if the first actually succeeded.

Error contract [#error-contract]

These strings are part of the CLI's contract surface. Operator runbooks grep for them; they will not be reworded without a deprecation pass.

| String                                                              | Cause                                                      | Fix                                                                                   |
| ------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `SAGEOX_TOKEN expired or invalid`                                   | PAT revoked, expired, or wrong environment                 | Rotate at [Settings → Tokens](https://sageox.ai/settings/tokens); update secret store |
| `SAGEOX_TOKEN not set; ox login is unavailable in this environment` | No PAT and no browser capability                           | Export `SAGEOX_TOKEN` (or run on a host with a browser)                               |
| `failed to clone team context: dial tcp: i/o timeout`               | Network allowlist gap                                      | Add team-context git host to platform's allowed hosts                                 |
| `ox: 'daemon' requires a long-lived helper and is unavailable here` | `ox daemon` invoked in a sandbox without `LongLivedHelper` | Use the inline command path — daemon is not required                                  |
| `ox: 'login' requires a browser and is unavailable here`            | `ox login` invoked without `Browser` capability            | Authenticate with [`SAGEOX_TOKEN`](/docs/cli/pats)                                    |
| `OX_EPHEMERAL=1 set but you appear to be on a persistent machine`   | Stray export from a previous CI experiment                 | Unset `OX_EPHEMERAL` in your shell                                                    |

Exit codes follow `sysexits.h`: usage errors are `64`, auth failures are `77`.

Claude Code Cloud [#claude-code-cloud]

Anthropic's [Managed Agents](https://platform.claude.com/docs/en/managed-agents/environments) run Claude Code in an isolated sandbox defined by an environment YAML.

Environment file [#environment-file]

```yaml
# .claude/environment.yml
packages:
  go: ["github.com/sageox/ox/cmd/ox@latest"]

secrets:
  SAGEOX_TOKEN: oxp_...    # paste your PAT here, or reference a secret

hooks:
  SessionStart: |
    ox agent prime
```

* `packages.go` installs `ox` from source into the sandbox image. Pin to a specific version (`@v0.42.0`) for reproducibility once you have one you like.
* `secrets.SAGEOX_TOKEN` is injected at runtime — it does not appear in the image layer. Prefer Anthropic's Secrets dashboard over inlining the value.
* `hooks.SessionStart` runs at the start of every Claude session. `ox` auto-detects the constrained environment from `CLAUDE_CODE_REMOTE` and configures itself accordingly — no `OX_EPHEMERAL` export needed.

Networking [#networking]

Claude Code Cloud defaults to `limited` egress. Add the SageOx hosts to your `allowed_hosts`:

```yaml
network:
  mode: limited
  allowed_hosts:
    - api.sageox.ai
    - install.sageox.ai
    - <your-team-context-git-host>   # gitlab.com, github.com, etc.
```

If you forget the team-context git host, `ox agent prime` will fail with a clone error pointing at the missing domain. Add it and retry.

Verify [#verify]

Start a Claude session and look for these lines in the SessionStart log:

```
ox agent prime: constrained environment (CLAUDE_CODE_REMOTE)
ox agent prime: authenticated as <your-email>
ox agent prime: team context loaded (<N> files)
```

If you see `SAGEOX_TOKEN expired or invalid`, rotate the PAT — see [Personal Access Tokens](/docs/cli/pats#expiration-and-rotation).

Devin [#devin]

Cognition's [Devin repo setup](https://docs.devin.ai/onboard-devin/repo-setup) splits configuration into three sections: setup (bakes into the snapshot), startup (runs every session), and secrets.

Setup commands [#setup-commands]

One-time, runs when Devin builds the workspace snapshot:

```bash
curl -fsSL https://install.sageox.ai/ox | sh
ox --version    # sanity check that install succeeded
```

Startup commands [#startup-commands]

Runs at the start of every Devin session, on top of the snapshot:

```bash
ox agent prime
```

`ox` auto-detects the Devin sandbox from `DEVIN_TASK_ID` and configures itself accordingly.

Secrets [#secrets]

In the Devin Secrets dashboard, add:

```
SAGEOX_TOKEN=oxp_...
```

Devin injects this into the environment before startup commands run. Never paste the token into the startup-commands box — that value is logged.

Notes [#notes]

* Devin keeps the snapshot warm across sessions, so `ox agent prime` runs against a pre-installed binary. If you bump `ox`, re-run the setup commands or invalidate the snapshot.

GitHub Actions [#github-actions]

For CI use cases — generating release notes, distilling sessions into team context, running scheduled queries:

```yaml
# .github/workflows/ox-distill.yml
name: ox distill
on:
  schedule:
    - cron: "0 6 * * *"     # daily at 06:00 UTC
  workflow_dispatch:

jobs:
  distill:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install ox
        run: curl -fsSL https://install.sageox.ai/ox | sh

      - name: Prime and distill
        env:
          SAGEOX_TOKEN: ${{ secrets.SAGEOX_TOKEN }}
        run: |
          ox agent prime
          ox distill --since=24h
```

For self-hosted runners with a persistent filesystem, `ox` will detect the persistent disk and use its normal local-state mode — you'll get better performance from the local CodeDB index without any config change.

GitHub Codespaces [#github-codespaces]

A Codespace has persistent disk and a multi-hour lifetime. Treat it as a slow laptop, not a sandbox:

```bash
# one-time workspace setup
curl -fsSL https://install.sageox.ai/ox | sh
ox login            # one-time, opens browser via Codespaces port-forward
ox init             # connects the workspace to your repo
```

After the first-run setup, every subsequent terminal session in the Codespace has `ox` configured from local state — no PAT needed, no per-session priming.

If you'd rather skip the device-code flow, export `SAGEOX_TOKEN` from a Codespaces secret instead of `ox login`. Both work; the device flow gives you the same credential refresh story as your laptop.

Troubleshooting [#troubleshooting]

SAGEOX_TOKEN expired or invalid [#sageox_token-expired-or-invalid]

The PAT has expired, been revoked, or is malformed. Create a new one at [Settings → Tokens](https://sageox.ai/settings/tokens) and update the secret in your agent platform. See [Personal Access Tokens](/docs/cli/pats#expiration-and-rotation).

401 Unauthorized against a custom endpoint [#401-unauthorized-against-a-custom-endpoint]

`ox` defaults to `https://api.sageox.ai`. If you set `SAGEOX_ENDPOINT` to a staging endpoint, confirm the PAT was issued against that same environment. PATs are bound to the endpoint they were issued for.

failed to clone team context: dial tcp: i/o timeout [#failed-to-clone-team-context-dial-tcp-io-timeout]

Network allowlist gap. The sandbox blocked the git host. For Claude Code Cloud, add the host to `network.allowed_hosts`. For Devin, check the workspace network settings. For GitHub Actions, this usually means a private team-context repo requires an SSH key — use HTTPS with a Git PAT instead.

ox: command not found [#ox-command-not-found]

The install script failed silently, often because `curl` was missing from a minimal base image. Use the platform's package manager to install `curl` first, then re-run the install script. For Devin, add the install step to the setup commands so it runs in a snapshot with a fuller toolchain.

OX_EPHEMERAL=1 set but you appear to be on a persistent machine [#ox_ephemeral1-set-but-you-appear-to-be-on-a-persistent-machine]

You exported `OX_EPHEMERAL=1` in a previous shell session and forgot. Unset it to restore full functionality. `ox doctor` will flag this on every invocation until you do.

Auto-detection misfired [#auto-detection-misfired]

If `ox` runs in constrained mode when you expect full mode (or vice versa), use the capability-level overrides above (`OX_PERSIST_DISK`, `OX_NO_DAEMON`, `OX_NETWORK`) rather than the coarse `OX_EPHEMERAL=1`. Run `ox doctor` to see which detection rule fired and what the resulting capabilities are.

Related [#related]

* [Personal Access Tokens](/docs/cli/pats) — the credential you need first
* [ox agent prime](/docs/cli/prime) — what gets loaded at session start
* [ox doctor](/docs/cli/doctor) — see capabilities for the current environment
* [SageOx + Codex cookbook](/docs/developers/codex) — recipes for OpenAI Codex
