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

# Environment variables

> Every variable the runner and the first-party engines read, and what each one does.

These variables configure the runner and first-party packages. In the tables,
`set` means the variable exists, `value` means it is not blank, and `flag`
means it is not blank, `0`, or `false`. Flag values are case-insensitive.

```bash
export E2E_USER_ADMIN_PASSWORD="$ADMIN_PASSWORD"
export E2E_CACHE_DIR=~/.cache/e2e
export E2E_TELEMETRY_DISABLED=1
```

## Reference

### Model

Configure a model in `e2e.config.ts`. Its provider reads the API key:
`gateway()` uses `AI_GATEWAY_API_KEY`, `openrouter()` uses
`OPENROUTER_API_KEY`, and `openai()` uses `OPENAI_API_KEY`. Without
`AI_GATEWAY_API_KEY`, `gateway()` uses a
[Vercel OIDC token](https://e2e.tester.army/docs/models#vercel-oidc-instead-of-a-key). A missing
credential fails on the first model call. Subscription models use a stored
login instead.
See [Models](https://e2e.tester.army/docs/models) and [Subscriptions](https://e2e.tester.army/docs/subscriptions).

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| [`E2E_OAUTH_CREDENTIALS`](#e2e_oauth_credentials) | `E2E_OAUTH_CREDENTIALS="$(cat oauth.json)"` | value | The stored subscription logins as JSON |
| [`OPENCODE_API_KEY`](#opencode_api_key) | `OPENCODE_API_KEY=<key>` | value | Replaces the stored `opencode-console` login |
| `XDG_CONFIG_HOME` | `XDG_CONFIG_HOME=~/.config` | value | Directory of the stored logins, `$XDG_CONFIG_HOME/e2e/oauth.json`; default `~/.config/e2e/oauth.json` |

#### `E2E_OAUTH_CREDENTIALS`

The stored subscription logins as JSON, the contents of `oauth.json`, for a
machine nobody can sign in on.

- The `e2e/oauth/*` models and `e2e models` read it in place of
  `$XDG_CONFIG_HOME/e2e/oauth.json`.
- `e2e login` and `e2e logout` refuse to change it.
- Refreshed tokens are not written back.

#### `OPENCODE_API_KEY`

An OpenCode Console service account API key or an OpenCode Go subscription
API key. `opencodeConsole()` and `e2e models opencode-console` use it in place
of the stored `opencode-console` login. OpenCode reads the same variable.

### Credentials and secrets

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| `E2E_USER_<NAME>_USERNAME` | `E2E_USER_ADMIN_USERNAME=admin@example.test` | set | Replaces `credentials.<name>.username` |
| `E2E_USER_<NAME>_PASSWORD` | `E2E_USER_ADMIN_PASSWORD="$ADMIN_PASSWORD"` | set | Replaces `credentials.<name>.password` |
| `E2E_SECRET_<NAME>` | `E2E_SECRET_API_KEY="$API_KEY"` | set | Replaces the value of `secrets.<name>` |

- `<NAME>` is the name uppercased with every character outside `A-Z0-9`
  replaced by `_`, so `power-user` becomes `E2E_USER_POWER_USER_USERNAME`.
- Two credentials, or two secrets, whose names map to one `<NAME>` are
  `INVALID_CONFIG` at config load, set or not. So one rotation never
  replaces two entries. The message names the entries and the variables.
  For example, `api-key` and `api_key` both read `E2E_SECRET_API_KEY`;
  `svc-a` and `svc_a` both read `E2E_USER_SVC_A_USERNAME` and `_PASSWORD`.
- A credential and a secret may share a name, since their prefixes differ.
- A variable for a name absent from the config is ignored.
- An empty password or secret is `INVALID_CONFIG`, and so is one shorter
  than 6 characters (code points). Redaction rewrites every occurrence of a
  value, and one that short would take ordinary text with it. An empty
  username is accepted.

### Run mode

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| [`CI`](#ci) | `CI=1` | flag | CI mode |

#### `CI`

Turns on CI mode:

- `retries` defaults to 1 and `workers` to 1.
- `cache` defaults to `read-only`.
- A focused test is `ONLY_IN_CI`.
- `reuseExisting` on an app command is ignored.

### Replay cache

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| [`E2E_CACHE_DIR`](#e2e_cache_dir) | `E2E_CACHE_DIR=~/.cache/e2e` | value | Replaces [`cache.dir`](https://e2e.tester.army/docs/reference/config#cache) |

#### `E2E_CACHE_DIR`

Replaces [`cache.dir`](https://e2e.tester.army/docs/reference/config#cache) for `e2e run` and
`e2e cache`, resolved from the project root.

- `~` or a leading `~/` is the home directory.
- `--cache-dir` wins over it.
- A custom `cache.store` ignores it.
- The cache mode stays as configured, and `--no-cache` still turns the cache
  off.

### Diagnostics

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| `NO_COLOR` | `NO_COLOR=1` | set | Plain CLI output. Without it, colors are on for a TTY or in CI, off when piped |
| `FORCE_COLOR` | `FORCE_COLOR=1` | set | Colored CLI output even when piped |
| `TERM` | `TERM=dumb` | value | `dumb` draws the `e2e init` wordmark at rest, without animation, as in CI |
| `GITHUB_SHA`, `GITHUB_HEAD_REF`, `GITHUB_REF_NAME` | `GITHUB_REF_NAME=main` | value | The commit and branch the report records under `run.vcs` when `git` cannot read the project root. `GITHUB_HEAD_REF` wins over `GITHUB_REF_NAME` |

`--debug` and `--ai-trace` are flags, not variables; see
[CLI](https://e2e.tester.army/docs/reference/cli#e2e-run).

### Telemetry

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| `E2E_TELEMETRY_DISABLED` | `E2E_TELEMETRY_DISABLED=1` | flag | Turns anonymous usage telemetry and the [update check](https://e2e.tester.army/docs/reference/cli#help-and-version) off. `e2e telemetry disable` saves the same choice for telemetry in the preferences file |
| `DO_NOT_TRACK` | `DO_NOT_TRACK=1` | flag | Same as `E2E_TELEMETRY_DISABLED` |
| `E2E_UPDATE_CHECK_DISABLED` | `E2E_UPDATE_CHECK_DISABLED=1` | flag | Turns the daily check for a newer e2e release off |
| `NO_UPDATE_NOTIFIER` | `NO_UPDATE_NOTIFIER=1` | flag | Same as `E2E_UPDATE_CHECK_DISABLED` |
| `npm_config_registry` | `npm_config_registry=https://npm.example.com/` | value | The registry [`e2e upgrade`](https://e2e.tester.army/docs/reference/cli#e2e-upgrade) reads versions from; default `https://registry.npmjs.org/`. Package managers set it for the commands they run |
| `E2E_TELEMETRY_DEBUG` | `E2E_TELEMETRY_DEBUG=1` | flag | Prints every telemetry event to stderr instead of sending it |
| [`E2E_TELEMETRY_FLEET`](#e2e_telemetry_fleet) | `E2E_TELEMETRY_FLEET=acme` | value | Names the platform running e2e for its users |
| `XDG_CONFIG_HOME` | `XDG_CONFIG_HOME=~/.config` | value | Directory of the preferences file and the update check's `update-check.json`, `$XDG_CONFIG_HOME/e2e`; default `~/.config/e2e` |
| `APPDATA` | `APPDATA=C:\Users\ada\AppData\Roaming` | value | The same on Windows, `%APPDATA%\e2e`; default `~/AppData/Roaming/e2e` |

#### `E2E_TELEMETRY_FLEET`

Names the platform running e2e for its users.

- Events are attributed to `fleet:<name>` instead of a per-machine id.
- No preferences file is written, and no notice is printed.
- A value that is not a plain token is sent as `other`.

#### Detected environments

Telemetry also checks for the markers CI vendors and coding agents set. Only
the derived name is sent.

| Markers | Reported as |
| --- | --- |
| `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `BUILDKITE`, `JENKINS_URL`, `TRAVIS`, `BITBUCKET_BUILD_NUMBER`, `TF_BUILD`, `TEAMCITY_VERSION`, `CODEBUILD_BUILD_ID`, `VERCEL`, `NETLIFY`, `DRONE`, `SEMAPHORE`, `APPVEYOR`, `WOODPECKER`, `EAS_BUILD`, `BITRISE_IO`, `CM_BUILD_ID`, `CI_XCODE_CLOUD` | `ci_name`: `github-actions`, `gitlab`, `circleci`, `buildkite`, `jenkins`, `travis`, `bitbucket`, `azure-pipelines`, `teamcity`, `aws-codebuild`, `vercel`, `netlify`, `drone`, `semaphore`, `appveyor`, `woodpecker`, `eas`, `bitrise`, `codemagic`, `xcode-cloud`; `unknown` when only `CI` is set |
| `CLAUDECODE`, `CLAUDE_CODE`, `CURSOR_AGENT`, `CURSOR_TRACE_ID`, `CODEX_SANDBOX`, `CODEX_THREAD_ID`, `CODEX_CI`, `GEMINI_CLI`, `COPILOT_GITHUB_TOKEN`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`, `OPENCODE_CLIENT`, `AUGMENT_AGENT`, `ANTIGRAVITY_AGENT`, `REPL_ID`, `AI_AGENT` | `coding_agent`: `claude-code`, `cursor`, `codex`, `gemini-cli`, `copilot`, `opencode`, `augment`, `antigravity`, `replit`, `other` |
| `WSL_DISTRO_NAME` | `wsl: true` |
| `npm_config_user_agent` | `package_manager`: the invoking package manager. `e2e init` reads the same variable to pick the install command |

What is sent and how to read it is on the [Telemetry](https://e2e.tester.army/docs/telemetry) page.

### Reporters

`@e2e-dev/github` reads the variables GitHub Actions sets, when a run finishes:

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| `GITHUB_ACTIONS` | `GITHUB_ACTIONS=true` | value | Without it the reporter posts nothing and says so |
| `GITHUB_REPOSITORY`, `GITHUB_RUN_ID`, `GITHUB_EVENT_NAME`, `GITHUB_WORKFLOW`, `GITHUB_JOB` | `GITHUB_REPOSITORY=acme/shop` | value | Always set by the runner. A missing one fails the reporter, naming the variable. The repository and run id build the artifacts link. The event name shows in the "not a pull request" row. The workflow and job join the marker a rerun finds its own comment by |
| `GITHUB_TOKEN`, `GH_TOKEN` | `GITHUB_TOKEN=<token>` | value | Posts the comment; the first one set wins. Unset, the job summary is still written. A fork's `pull_request` token is read-only and cannot post; see [When nothing is posted](https://e2e.tester.army/docs/github#when-nothing-is-posted) |
| `GITHUB_EVENT_PATH`, `GITHUB_REF` | `GITHUB_REF=refs/pull/42/merge` | value | The pull request number and head commit from the event payload, or the number from `refs/pull/N/merge` |
| `GITHUB_SHA` | `GITHUB_SHA=<commit>` | value | The commit source links point at when the event is not about a pull request |
| `GITHUB_WORKSPACE` | `GITHUB_WORKSPACE=/home/runner/work/shop/shop` | value | The checkout root. Source links add the project's path inside it, so a suite in a subdirectory links to the right file |
| `GITHUB_SERVER_URL`, `GITHUB_API_URL` | `GITHUB_SERVER_URL=https://github.example.com` | value | The GitHub host, for Enterprise Server. Default `https://github.com` and `https://api.github.com` |
| `GITHUB_STEP_SUMMARY` | `GITHUB_STEP_SUMMARY=<file>` | value | The job summary file the comment text is appended to |

### Engines

| Variable | Example | Reading | Effect |
| --- | --- | --- | --- |
| `PLAYWRIGHT_BROWSERS_PATH` | `PLAYWRIGHT_BROWSERS_PATH=~/.cache/ms-playwright` | value | Playwright's browser cache, honored by the engine's `prepare` hook |
| `PLAYWRIGHT_SKIP_BROWSER_GC` | `PLAYWRIGHT_SKIP_BROWSER_GC=0` | value | Defaults to `1` for the engine's browser install, which keeps other browsers in the cache. `0` lets the install remove them |
| `KERNEL_API_KEY` | `KERNEL_API_KEY=<key>` | value | The API key `kernel()` from `@e2e-dev/kernel` creates browsers with. Unset, the browser lease fails. See [Kernel](https://e2e.tester.army/docs/integrations/kernel) |
| `LIGHTPANDA_PATH` | `LIGHTPANDA_PATH=/opt/lightpanda` | value | The binary `lightpanda()` from `@e2e-dev/lightpanda` starts a CDP server with. Unset, `lightpanda` on `PATH`, `~/.lightpanda`, or `~/.local/bin`. See [Lightpanda](https://e2e.tester.army/docs/integrations/lightpanda) |
| [`EXPO_TOKEN`](#expo_token) | `EXPO_TOKEN=<token>` | value | The Expo access token `easSimulators()` from `@e2e-dev/eas` uses |
| `HOME`, `USERPROFILE` | `HOME=/home/ada` | value | Where `easSimulators()` finds the eas-cli login without `EXPO_TOKEN`: `USERPROFILE` on Windows, `HOME` elsewhere, the OS user's home when unset |
| `LIMRUN_API_KEY` | `LIMRUN_API_KEY=<key>` | value | The API key `limrun()` from `@e2e-dev/limrun` leases devices with. Unset, the lease fails with `INVALID_CONFIG`. See [Limrun](https://e2e.tester.army/docs/integrations/limrun) |
| `LIMRUN_BASE_URL` | `LIMRUN_BASE_URL=<url>` | value | The Limrun API host. Default `https://api.limrun.com` |
| `LT_USERNAME`, `LT_ACCESS_KEY` | `LT_USERNAME=acme` | value | The TestMu AI username and access key `testmu()` from `@e2e-dev/testmu` leases devices with. With either unset, the device lease fails. `testmuBrowsers()` from `@e2e-dev/testmu/web` puts them only in the CDP URL of each browser session, and with either unset the browser lease fails. See [TestMu AI](https://e2e.tester.army/docs/integrations/testmu) |
| `TESTMU_API_ENDPOINT` | `TESTMU_API_ENDPOINT=<url>` | value | The TestMu AI sessions API base URL `testmu()` looks up session recordings at. Default `https://mobile-api.lambdatest.com/mobile-automation/api/v1`. A value that is not an http(s) URL, or carries a username or password, fails the lookup with `INVALID_CONFIG`. agent-device's TestMu AI plugin reads it too |
| `E2E_WEB_BROWSERS_<TARGET>_<digest>` | - | value | `@e2e-dev/web` writes it in `prepare`; each worker reads it to find its leased browser. Don't set it by hand |
| `E2E_AGENT_DEVICE_POOL_<TARGET>_<digest>` | - | value | `@e2e-dev/mobile` writes it in `prepare`; each worker reads it to find its booted device. Don't set it by hand |

#### `EXPO_TOKEN`

The Expo access token `easSimulators()` from `@e2e-dev/eas` starts and stops
simulator sessions with. See [EAS Simulators](https://e2e.tester.army/docs/integrations/eas).

- Unset, it uses the eas-cli login in `.expo/state.json` under the home
  directory ([`HOME`, `USERPROFILE`](#engines)).
- With neither, the device lease fails.

## What app processes inherit

An app `command` receives `PATH`, `HOME`, `TMPDIR`, `TMP`, `TEMP`,
`SystemRoot`, and `COMSPEC`, plus the variables `command.inheritEnv` matches
and whatever `command.env` sets.

The scaffolded config module reads `APP_URL`, not the runner. If the dev
server needs it too, pass it through `command.env`.
