# Environment variables

> GROK_BOT_018_APP, GROK_BOT_OUTPUT_APP_NAME, SAND_DISABLE_*, OPENROUTER_API_KEY, SAND_OPENROUTER_MODEL, CODEX_HOME, CLAUDE_CODE_PATH, and binding-manifest overrides.

- Repository: sashimikun/grok-bot-0.18-reconstructed
- GitHub: https://github.com/sashimikun/grok-bot-0.18-reconstructed
- Human docs: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-grok-bot-0-18-reconstructed-c774cc9a5c15/llms-full.txt

## Source Files

- `scripts/lib/config.mjs`
- `scripts/bootstrap-runtime.mjs`
- `scripts/lib/build-asar.mjs`
- `source/host/extensions/inference/provider-session.ts`
- `source/shared/node/inference-router-local.ts`
- `scripts/clean-build.mjs`

---

---
title: "Environment variables"
description: "GROK_BOT_018_APP, GROK_BOT_OUTPUT_APP_NAME, SAND_DISABLE_*, OPENROUTER_API_KEY, SAND_OPENROUTER_MODEL, CODEX_HOME, CLAUDE_CODE_PATH, and binding-manifest overrides."
---

`process.env` is read in three layers: Node packaging scripts (`scripts/bootstrap-runtime.mjs`, `scripts/lib/config.mjs`, `scripts/lib/build-asar.mjs`, `scripts/clean-build.mjs`), the reconstructed Electron-main guard prepended into `dist/electron-main/main.cjs`, and the host inference clients in `source/host/extensions/inference/provider-session.ts` plus `source/shared/node/inference-router-local.ts`. Unset packaging keys fall back to checksum-pinned cache paths; unset inference keys fall back to Settings secrets or local CLI homes; reconstructed `SAND_DISABLE_*` defaults use `??=` so an explicit launch value still wins.

## Bootstrap and package identity

<ParamField body="GROK_BOT_018_APP" type="string">
Absolute or resolvable path to an existing `Grok Bot.app`. `npm run bootstrap` validates `CFBundleShortVersionString` against `0.18.0`, copies the tree with `ditto` into `.cache/runtime/Grok Bot.app`, then hydrates `src/app/dist` from that app’s `app.asar`. `resolveRuntimeApp()` uses the same variable during package/ASAR builds without downloading a DMG.
</ParamField>

<ParamField body="GROK_BOT_OUTPUT_APP_NAME" type="string">
Optional output bundle file name. `scripts/lib/config.mjs` takes `path.basename()` of the trimmed value and writes `dist/<name>`. Default is `Grok Bot 0.18 Reconstructed.app`.
</ParamField>

```mermaid
flowchart TD
  start["npm run bootstrap"] --> env{"GROK_BOT_018_APP trimmed?"}
  env -->|yes| cacheApp["validate 0.18.0 then ditto into .cache/runtime/Grok Bot.app"]
  env -->|no| cached{"cachedRuntimeApp exists?"}
  cached -->|yes| validateCached["validate .cache/runtime/Grok Bot.app"]
  cached -->|no| dmg["LFS archived DMG if checksum matches, else public 0.18.0 URL"]
  dmg --> extract["hdiutil attach then cache Grok Bot.app"]
  cacheApp --> hydrate["hydrate src/app/dist from app.asar"]
  validateCached --> hydrate
  extract --> hydrate
```

| Variable | Consumer | Default when unset | Constraint |
|---|---|---|---|
| `GROK_BOT_018_APP` | `scripts/bootstrap-runtime.mjs`, `scripts/lib/runtime.mjs` | `.cache/runtime/Grok Bot.app`, else DMG extract | Must be 0.18.0; must contain `Contents/MacOS/Grok Bot` and `app.asar.unpacked` |
| `GROK_BOT_OUTPUT_APP_NAME` | `scripts/lib/config.mjs` | `Grok Bot 0.18 Reconstructed.app` | Only the basename is used |

<RequestExample>
```bash
GROK_BOT_018_APP="/Applications/Grok Bot.app" npm run bootstrap
GROK_BOT_OUTPUT_APP_NAME="Grok Bot 0.18 Reconstructed.app" npm run package
```
</RequestExample>

<Warning>
`GROK_BOT_018_APP` that is not `0.18.0` fails with `Expected Grok Bot 0.18.0, got <version> at <path>`. Packaging without a cached runtime and without this variable fails with `Missing 0.18.0 runtime. Run npm run bootstrap first.`
</Warning>

## Reconstructed `SAND_DISABLE_*` guard

`scripts/lib/build-asar.mjs` exports `reconstructedUpdaterGuard` and prepends it onto reconstructed Electron-main source (artifact fallback and clean production bundle when `reconstructedPackage: true`). The injected prefix is:

```js
process.env.SAND_DISABLE_UPDATES ??= "1";
process.env.SAND_DISABLE_SENTRY ??= "1";
process.env.SAND_DISABLE_TELEMETRY ??= "1";
```

`??=` assigns only when the key is `null` or `undefined`. A launch-time value is kept. Runtime checks treat disable as exact `"1"`:

| Variable | Effect when `"1"` | Effect when not `"1"` |
|---|---|---|
| `SAND_DISABLE_UPDATES` | `computeUpdateDisabledReason` sets `envDisabled: true` | Updater disable reason does not come from this env key |
| `SAND_DISABLE_SENTRY` | Sentry adapters skip init (`SAND_DISABLE_SENTRY !== "1"` is false) | Sentry can initialize |
| `SAND_DISABLE_TELEMETRY` | Desktop/host structured-log telemetry is disabled | Telemetry adapters run |

`npm run smoke` also forces `SAND_DISABLE_ANALYTICS=1` on the isolated native process. That key is **not** part of `reconstructedUpdaterGuard`.

## Inference credentials and models

Host routing reads credentials from the process environment first, then from `box-secrets.json` under `getSandRootDir()` for OpenRouter only. Settings → Router stores the OpenRouter secret as `OPENROUTER_API_KEY` via `window.desktop.secrets.upsert`.

<ParamField body="OPENROUTER_API_KEY" type="string" required>
Required when `inferenceProvider` is `openrouter`. Resolution: `process.env.OPENROUTER_API_KEY`, else `persistedSecrets().OPENROUTER_API_KEY`. Empty after both lookups throws `OpenRouter needs OPENROUTER_API_KEY. Add it in Settings → Router.`
</ParamField>

<ParamField body="SAND_OPENROUTER_MODEL" type="string">
OpenRouter model id passed to `createOpenAI({ baseURL: "https://openrouter.ai/api/v1" }).chat(...)`. Default `openai/gpt-5.2`.
</ParamField>

<ParamField body="CODEX_HOME" type="string">
Directory for Codex `auth.json` and `config.toml`. Default `~/.codex`. `auth.json` must be a non-symlink regular file with no group/other bits (`mode & 0o077 === 0`). Tokens must have `auth_mode === "chatgpt"` plus non-empty `access_token`, `refresh_token`, `id_token`, and `account_id`.
</ParamField>

<ParamField body="CLAUDE_CODE_PATH" type="string">
First candidate for the Claude Code executable. If missing, search continues at `~/.local/bin/claude`, `~/.claude/local/claude`, `PATH`, `/opt/homebrew/bin/claude`, `/usr/local/bin/claude`. Missing executable throws `Claude Code is not installed. Install and sign in to Claude Code, then reopen Grok Bot.`
</ParamField>

| Variable | Provider | Default | Notes |
|---|---|---|---|
| `OPENROUTER_API_KEY` | `openrouter` | none | Env beats Settings secret |
| `SAND_OPENROUTER_MODEL` | `openrouter` | `openai/gpt-5.2` | Used for both `getModelId()` and the chat client |
| `CODEX_HOME` | `codex` | `~/.codex` | Auth file `$CODEX_HOME/auth.json`; model/effort may come from `$CODEX_HOME/config.toml` |
| `SAND_CODEX_MODEL` | `codex` | `config.toml` `model`, else `gpt-5.4` | Env wins over TOML |
| `SAND_CODEX_REASONING_EFFORT` | `codex` | `config.toml` `model_reasoning_effort` | Allowed: `minimal`, `low`, `medium`, `high`, `xhigh` |
| `CLAUDE_CODE_PATH` | `claude-code` | discovered CLI | Passed as `pathToClaudeCodeExecutable` to `@anthropic-ai/claude-agent-sdk` |
| `SAND_CLAUDE_MODEL` | `claude-code` | SDK default | Optional `query()` `model` option |
| `CODEX_PATH` | status only | discovered `codex` binary | Codex HTTP calls do not spawn this CLI |
| `ANTHROPIC_API_KEY` | `claude-code` status | none | Marks `authenticated` if `.claude/.credentials.json` is absent |

<Note>
The renderer Router panel does not collect `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`. Codex uses the private ChatGPT login already stored by Codex; Claude Code uses the local CLI login (or `ANTHROPIC_API_KEY` for status only).
</Note>

## Binding-manifest overrides

Clean production activation is fail-closed. Env (or a CLI path on the activation scripts) supplies extra `schemaVersion` 1 JSON; unbound required slots keep the artifact fallback.

<ParamField body="GROK_BOT_HOST_BINDINGS_MANIFEST" type="string">
Path to a host production binding manifest. `buildProductionHostIfSupplied` and `buildCleanDistribution` read `process.env.GROK_BOT_HOST_BINDINGS_MANIFEST`. There is no checked-in default file. Manifest must be `{ schemaVersion: 1, bindings: [...] }`. Direct CLI: `node scripts/host-production-activation.mjs [manifest]`.
</ParamField>

<ParamField body="GROK_BOT_ELECTRON_MAIN_BINDINGS_MANIFEST" type="string">
Path to an Electron-main production binding manifest. Resolution order for packaging: this env var, else `manifests/reconstruction/electron-main-production-bindings-manifest.json` if that file exists. Direct activation CLI prefers `argv[2]` over the env var. Residual unbound slots stay fail-closed; no native default is inferred for missing slots.
</ParamField>

| Surface | Env | Default path | Failure |
|---|---|---|---|
| Host `dist/host/host-main.cjs` | `GROK_BOT_HOST_BINDINGS_MANIFEST` | none | `incomplete-evidence-derived-manifest` listing unbound mandatory bindings |
| Electron-main `dist/electron-main/main.cjs` | `GROK_BOT_ELECTRON_MAIN_BINDINGS_MANIFEST` | `manifests/reconstruction/electron-main-production-bindings-manifest.json` | `Supply a schemaVersion 1 manifest for those slots through GROK_BOT_ELECTRON_MAIN_BINDINGS_MANIFEST` |

Clean Electron-main packaging still prepends `reconstructedUpdaterGuard` after esbuild when `reconstructedPackage` is true.

## Renderer and lab-build packaging

These keys change what `buildAsar()` stages. They are packaging-time, not Settings.

| Variable | Value | Behavior |
|---|---|---|
| `GROK_BOT_RENDERER_SOURCE` | path relative to repo root | Replaces staged `dist/renderer` after requiring `index.html` at that path |
| `GROK_BOT_BUILD_DEV_APP` | `"1"` | Sets staged `package.json` `sandLab: true` and `productName` `Grok Bot 0.18 Dev`; enables DevTools / control-server seams |
| `GROK_BOT_RECONSTRUCTED_DEV` | `"1"` | Runtime seam: treat as sand-lab, skip single-instance lock, open DevTools gate. Stripped from `npm run smoke` |

Without `GROK_BOT_RENDERER_SOURCE`, the packaged UI remains the patched shipped renderer.

## Isolated native launch env

`createNativeTestEnvironment()` copies the parent env, deletes the production denylist, then sets:

```text
SAND_USER_DATA_DIR=<absolute isolated root>
SAND_DATA_ROOT=<isolated root>/sand-data
SAND_DISABLE_UPDATES=1
SAND_DISABLE_TELEMETRY=1
SAND_DISABLE_ANALYTICS=1
```

Denylist keys include `GROK_BOT_RECONSTRUCTED_DEV`, `SAND_DEV_LOGIN`, `SAND_BACKEND_URL`, `CURSOR_API_BASE_URL`, `SAND_HOST_GATEWAY_URL`, `SAND_HOST_GATEWAY_TOKEN`, `SAND_FEATURE_GATE_OVERRIDES`, and `SAND_MODEL_EXPERIMENT_OVERRIDE`. `SAND_USER_DATA_DIR` is also honored by desktop bootstrap (`--user-data-dir` wins over the env key). `SAND_DATA_ROOT` must be an absolute path to override `getSandRootDir()`.

Local Docker injects container env (`SAND_BOX_AUTO_UPDATE=0`, `SAND_GATEWAY_BIND_HOST=0.0.0.0`, `SAND_HOST_PORT=1340`, …) when creating `grok-bot-local-vm`. Those are not packaging-script variables.

## Errors

| Condition | Message / status |
|---|---|
| `GROK_BOT_018_APP` wrong version | `Expected Grok Bot 0.18.0, got …` |
| Incomplete `.app` | `Incomplete Grok Bot runtime at …` |
| No runtime for package | `Missing 0.18.0 runtime. Run npm run bootstrap first.` |
| Missing OpenRouter key | `OpenRouter needs OPENROUTER_API_KEY. Add it in Settings → Router.` |
| Codex `auth.json` world-readable, symlink, or non-file | `Codex login credentials must be a private direct regular file.` |
| Codex not ChatGPT-signed-in | `Codex is not signed in with ChatGPT. Run codex login, then reopen Grok Bot.` |
| Claude CLI missing | `Claude Code is not installed. …` |
| Host/Electron-main manifest not `schemaVersion` 1 | `… binding manifest must use schemaVersion 1 and a bindings array` |
| Renderer override missing `index.html` | `readFile` fails on the override path |
| Dev/runtime seam anchors drifted | `Cannot enable reconstructed … seam; upstream anchor changed` |

## Next

<CardGroup>
  <Card title="Bootstrap the pinned runtime" href="/bootstrap-pinned-runtime">
    `GROK_BOT_018_APP` vs cache vs LFS DMG vs public URL, plus SHA-256 gates.
  </Card>
  <Card title="Choose an inference provider" href="/choose-inference-provider">
    Persist `inferenceProvider` and satisfy Cursor, Claude Code, Codex, or `OPENROUTER_API_KEY`.
  </Card>
  <Card title="Package the macOS app" href="/package-macos-app">
    Output name, updater guard injection, and reconstructed identity.
  </Card>
  <Card title="Binding and renderer overrides" href="/edit-frontend-workspace">
    `GROK_BOT_RENDERER_SOURCE` versus the patched shipped renderer.
  </Card>
  <Card title="Verify and smoke" href="/verify-and-smoke">
    Isolated `SAND_DISABLE_*` and the production env denylist.
  </Card>
  <Card title="Router failures" href="/router-failures">
    Missing keys, private `auth.json`, and Claude Code install errors.
  </Card>
</CardGroup>
