# Environment variables

> Documented IMPECCABLE_* and related env vars for context dir, update host/cache, live config, hook quiet/disable/depth/log, telemetry opt-out, and OpenCode config dirs.

- Repository: pbakaus/impeccable
- GitHub: https://github.com/pbakaus/impeccable
- Human docs: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4
- Complete Markdown: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4/llms-full.txt

## Source Files

- `skill/scripts/context.mjs`
- `skill/scripts/hook-lib.mjs`
- `skill/scripts/lib/impeccable-paths.mjs`
- `skill/scripts/lib/staleness-notice.mjs`
- `cli/bin/commands/skills.mjs`
- `skill/scripts/concept-seed.mjs`

---

---
title: "Environment variables"
description: "Documented IMPECCABLE_* and related env vars for context dir, update host/cache, live config, hook quiet/disable/depth/log, telemetry opt-out, and OpenCode config dirs."
---

Runtime and install code reads environment variables for path overrides, network hosts, hook behavior, telemetry, and provider config directories. Most day-to-day settings belong in `.impeccable/config.json` or `config.local.json`; env vars are session overrides, power-user escapes, and CI/test knobs. Truthy string values for hook switches match `/^(1|true|yes|on)$/i`.

## Precedence overview

| Concern | Env var | Config key | Precedence |
|---|---|---|---|
| Disable design hook | `IMPECCABLE_HOOK_DISABLED` | `hook.enabled: false` | Env skips the hook for this process; config is project-persistent |
| Quiet clean/pending acks | `IMPECCABLE_HOOK_QUIET` | `hook.quiet: true` | Env or config either enables quiet |
| Hook audit NDJSON | `IMPECCABLE_HOOK_LOG` | `hook.auditLog` | Env wins when set |
| Skill update check | `IMPECCABLE_NO_UPDATE_CHECK` | `updateCheck: false` | Either disables boot network check |
| Staleness boot notice | `IMPECCABLE_NO_STALENESS_CHECK` | `stalenessCheck: false` | Either disables; local config overrides shared when both set |
| Live config path | `IMPECCABLE_LIVE_CONFIG` | (file location only) | Env overrides `.impeccable/live/config.json` and legacy script path |
| PRODUCT/DESIGN location | `IMPECCABLE_CONTEXT_DIR` | (filesystem layout) | Used only when default discovery finds no artifacts |

<Note>
Prefer project config for durable choices (`hooks off`, quiet mode, audit path). Use env vars for one-shell overrides, sandboxes, and CI.
</Note>

## Context directory

`context.mjs` resolves `PRODUCT.md` / `DESIGN.md` in a fixed order: active project root, then `.agents/context/` and `docs/`, then monorepo/repo-root inheritance. Only if those yields are empty does it consult the env override.

<ParamField body="IMPECCABLE_CONTEXT_DIR" type="string">
Absolute path or path relative to the process cwd. Trimmed; blank/whitespace is ignored. When set and defaults are empty, `PRODUCT.md` / `DESIGN.md` (case variants) are looked up in that directory. Relative values resolve with `path.resolve(cwd, trimmed)`.
</ParamField>

```bash
# Artifacts live outside the usual project root
export IMPECCABLE_CONTEXT_DIR=/path/to/brand-docs
node skill/scripts/context.mjs
```

## Update check (boot)

On session boot, `context.mjs` may append an `UPDATE_AVAILABLE` directive after a throttled poll (at most once per day; same version re-notified at most weekly). Failures are silent and never block context output.

<ParamField body="IMPECCABLE_UPDATE_HOST" type="string" default="https://impeccable.style">
Base URL for the latest-skill version poll. Trailing slashes are stripped.
</ParamField>

<ParamField body="IMPECCABLE_UPDATE_CACHE" type="string" default="~/.impeccable/update-check.json">
JSON cache path for `lastCheck`, `latestVersion`, and anti-nag stamps. Lives under the user home by default so clones do not share dismissals.
</ParamField>

<ParamField body="IMPECCABLE_NO_UPDATE_CHECK" type="string">
Any non-empty value skips the update check entirely for that process. Equivalent project config: `"updateCheck": false` in `.impeccable/config.json` or `config.local.json` (local wins when both define the key).
</ParamField>

```bash
# Offline / test harnesses that assert on other boot directives
export IMPECCABLE_NO_UPDATE_CHECK=1
export IMPECCABLE_NO_STALENESS_CHECK=1
```

## Staleness boot notice

Tier-1 schema findings can emit one `CONTEXT_STALE` directive at boot. Mention/route findings are throttled to about once per week per project; `auto` findings are not user-facing.

<ParamField body="IMPECCABLE_NO_STALENESS_CHECK" type="string">
Any non-empty value disables the boot staleness pass. Project config alternative: `"stalenessCheck": false`. `doctor` still runs on demand when the boot check is off.
</ParamField>

<ParamField body="IMPECCABLE_STALENESS_CACHE" type="string" default="~/.impeccable/staleness-check.json">
Throttle state path (alongside the update cache under `~/.impeccable/`). Useful for sandboxed tests that must not touch the real home cache.
</ParamField>

## Live config path

Live mode resolves its config through `resolveLiveConfigPath` in `skill/scripts/lib/impeccable-paths.mjs`.

| Priority | Location |
|---|---|
| 1 | `IMPECCABLE_LIVE_CONFIG` (absolute, or cwd-relative) |
| 2 | `.impeccable/live/config.json` if present |
| 3 | Legacy `<scriptsDir>/config.json` if `scriptsDir` is provided and the file exists |
| 4 | Default path `.impeccable/live/config.json` (even if missing yet) |

<ParamField body="IMPECCABLE_LIVE_CONFIG" type="string">
Override path for live `config.json`. Trimmed; blank is ignored.
</ParamField>

```bash
export IMPECCABLE_LIVE_CONFIG=./fixtures/live-config.json
```

Additional live-only knobs (defaults shown):

| Variable | Default | Role |
|---|---|---|
| `IMPECCABLE_LIVE_DEBUG_EVENTS` | off | `1`/`true`/`yes` logs manual-edit debug events in the live server |
| `IMPECCABLE_LIVE_COPY_AGENT_TIMEOUT_MS` | `120000` | Copy-edit agent timeout for manual-edit commit |
| `IMPECCABLE_LIVE_APPLY_EVENT_HARD_TIMEOUT_MS` | `150000` | Hard timeout for apply-event work |
| `IMPECCABLE_LIVE_APPLY_EVENT_SOFT_DEADLINE_MS` | `120000` | Soft deadline for apply-event work |
| `IMPECCABLE_LIVE_SVELTE_COMPONENT` | on | Set to `0`/`false`/`no` to disable Svelte component injection path |

## Design hook

Hook runtime settings prefer `.impeccable/config.json` → `hook.*`, with these env overrides still honored. `hook-admin.mjs status` prints the current `IMPECCABLE_HOOK_DISABLED` env state.

### Disable, quiet, log

<ParamField body="IMPECCABLE_HOOK_DISABLED" type="string">
Truthy (`1`, `true`, `yes`, `on`) skips PostToolUse, Stop, and Cursor pre-edit hooks for this process (`skipped: env-disabled`). Prefer `/impeccable hooks off` for a durable project disable (`hook.enabled: false`).
</ParamField>

<ParamField body="IMPECCABLE_HOOK_QUIET" type="string">
Truthy silences clean and pending acks on the per-edit path. Findings still emit. Config alternative: `hook.quiet: true`.
</ParamField>

<ParamField body="IMPECCABLE_HOOK_LOG" type="string">
Path for one NDJSON line per hook invocation. Env wins over `hook.auditLog`. Supports `~/…`, absolute paths, and project-relative paths (resolved against the edited project root when known).
</ParamField>

```bash
# One-shot quiet + audit for this shell
export IMPECCABLE_HOOK_QUIET=1
export IMPECCABLE_HOOK_LOG=~/.impeccable/hook.ndjson
```

### Depth, debug, harness

<ParamField body="IMPECCABLE_HOOK_DEPTH" type="string">
Re-entrancy guard. `hook.mjs` snapshots the parent env, then sets `IMPECCABLE_HOOK_DEPTH` (default `1`) for descendants. If the inherited env already has depth set (`1`/`true`/`yes`/`on` or a positive integer), the hook returns immediately as reentrant. `CLAUDE_HOOK_DEPTH` is checked the same way.
</ParamField>

<ParamField body="IMPECCABLE_HOOK_DEBUG" type="string">
When set, unexpected hook errors write a short message to stderr (exit code remains 0 so the agent turn is not broken).
</ParamField>

<ParamField body="IMPECCABLE_HOOK_HARNESS" type="string">
Force harness identity: `cursor`, `github`, or `claude`/`codex`. Otherwise the event shape is inferred.
</ParamField>

## Telemetry and concept seed

Choice telemetry is a fire-and-forget POST to the roll API’s `/chosen` endpoint when a dealt world is selected. It does not run for non-API seeds and never fails the caller.

<ParamField body="IMPECCABLE_NO_TELEMETRY" type="string">
Any truthy presence disables the choice ping.
</ParamField>

<ParamField body="DO_NOT_TRACK" type="string">
Standard opt-out; same effect as `IMPECCABLE_NO_TELEMETRY` for the choice ping.
</ParamField>

Related concept-seed networking and catalog vars:

| Variable | Default | Role |
|---|---|---|
| `IMPECCABLE_CATALOG_DIR` | skill scripts dir | Local catalog root (`concept-ingredients.json`, reviews, compositions) for private repos, evals, tests |
| `IMPECCABLE_API_URL` | `https://impeccable.style/api` | Roll/chosen API base |
| `IMPECCABLE_API_TIMEOUT` | `4000` | Shared deadline (ms) for all API calls in one seed run |
| `IMPECCABLE_CARD_BASE` | `https://impeccable.style/worlds/cards` | World card image base URL |
| `IMPECCABLE_CONCEPT_SEED` | random 4-byte hex | Deterministic deal key when not passed explicitly |
| `IMPECCABLE_COMPOSITIONS` | off | Set to `1` to include compositions in rendered deals (catalog development) |

```bash
export IMPECCABLE_NO_TELEMETRY=1
# or
export DO_NOT_TRACK=1
```

## Skills install: OpenCode and bundles

Global OpenCode skills are **not** installed under `~/.opencode`. The CLI resolves OpenCode’s config directory as:

1. `$OPENCODE_CONFIG_DIR` if set  
2. else `$XDG_CONFIG_HOME/opencode`  
3. else `~/.config/opencode`  

Global skills land in `<that-dir>/skills`. Project scope remains `.opencode/skills`.

<ParamField body="OPENCODE_CONFIG_DIR" type="string">
OpenCode config root override. Skills install uses `join(OPENCODE_CONFIG_DIR, 'skills')` for the global target.
</ParamField>

<ParamField body="XDG_CONFIG_HOME" type="string">
When `OPENCODE_CONFIG_DIR` is unset, OpenCode global config is `$XDG_CONFIG_HOME/opencode`.
</ParamField>

<ParamField body="IMPECCABLE_BUNDLE_PATH" type="string">
Local universal skill bundle (directory or zip) for `npx impeccable` install/update instead of downloading from the network. Used by smoke tests and offline install.
</ParamField>

Other install/CLI environment notes:

| Variable | Role |
|---|---|
| `NO_COLOR` | When defined, skills CLI color output is disabled (also when `TERM=dumb` or stdout is not a TTY) |
| `CI` | URL detect launches Chromium with `--no-sandbox`; `serve-question` treats the session as headless unless forced |

## Question UI and image generation

| Variable | Role |
|---|---|
| `IMPECCABLE_QUESTION_DISABLED` | `serve-question` exits 2 and tells the agent to use the structured question tool |
| `IMPECCABLE_QUESTION_FORCE` | Serve browser questions even in CI/SSH/headless Linux (no `DISPLAY`/`WAYLAND_DISPLAY`) |
| `OPENAI_API_KEY` | Enables API fallback for `generate-image.mjs`; boot may emit `IMAGE_GEN_AVAILABLE` when set |
| `IMPECCABLE_IMAGE_GEN_FAKE` | Offline fake PNG/SVG for tests (no API call) |
| `IMPECCABLE_PALETTE_SEED` | Same as `palette.mjs --from`; deterministic palette pick for evals |
| `IMPECCABLE_CRITIQUE_META` | Optional meta payload for critique storage |

## Test and CI suite knobs

These are for repository tests and maintainers, not product configuration:

| Variable | Suite / tool | Role |
|---|---|---|
| `IMPECCABLE_E2E_ONLY` | live-e2e | Fixture name filter |
| `IMPECCABLE_E2E_DEBUG` | live-e2e | Dump DOM / dev-server tail on failure |
| `IMPECCABLE_E2E_AGENT` | live-e2e | `fake` (default) or `llm` |
| `IMPECCABLE_E2E_LLM_PROVIDER` / `_MODEL` / `_EFFORT` | live-e2e LLM agent | Provider and model overrides |
| `IMPECCABLE_E2E_CONSOLE` | live-e2e | Capture browser console |
| `IMPECCABLE_E2E_ARTIFACT_DIR` | live-e2e | Artifact root |
| `IMPECCABLE_SKILL_BEHAVIOR_MODELS` | skill-behavior | Restrict provider ids |
| `IMPECCABLE_SKILL_BEHAVIOR_VERBOSE` | skill-behavior | Dump per-scenario traces |
| `IMPECCABLE_CLI_REMOTE_E2E` | skills remote E2E | Opt-in remote install tests |
| Provider API keys | skill-behavior / live LLM | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, etc.; missing keys skip cleanly |

## Common setups

```bash
# CI / offline session: no network nags, no telemetry
export IMPECCABLE_NO_UPDATE_CHECK=1
export IMPECCABLE_NO_STALENESS_CHECK=1
export IMPECCABLE_NO_TELEMETRY=1

# Debug the design hook for one shell
export IMPECCABLE_HOOK_LOG=/tmp/impeccable-hook.ndjson
export IMPECCABLE_HOOK_DEBUG=1
# optional: export IMPECCABLE_HOOK_QUIET=1

# Point context at nonstandard docs
export IMPECCABLE_CONTEXT_DIR=./brand

# Offline skill install from a local universal bundle
export IMPECCABLE_BUNDLE_PATH=./dist/universal.zip
npx impeccable skills install
```

## Troubleshooting

| Symptom | Check |
|---|---|
| Hook still fires after “disable” | Confirm `IMPECCABLE_HOOK_DISABLED` is truthy (`1`/`true`/`yes`/`on`), or use project `hooks off`. Non-matching strings do not disable. |
| Update banner every session | Cache unwritable under `~/.impeccable/`; set `IMPECCABLE_UPDATE_CACHE` to a writable path or disable with `IMPECCABLE_NO_UPDATE_CHECK` / `updateCheck: false`. |
| `IMPECCABLE_CONTEXT_DIR` ignored | Env is only used when no PRODUCT/DESIGN was found via project, fallback dirs, or monorepo inheritance. |
| OpenCode install missing skills | Ensure install targeted `$OPENCODE_CONFIG_DIR` / XDG / `~/.config/opencode`, not `~/.opencode`. |
| Choice pings still sent | Set `IMPECCABLE_NO_TELEMETRY` or `DO_NOT_TRACK`; pings only fire for API-dealt rolls. |
| serve-question exits 2 in CI | Expected without a browser; set `IMPECCABLE_QUESTION_FORCE=1` only if you intentionally need the HTTP UI. |

## Related pages

<CardGroup>
  <Card title="Configuration reference" href="/configuration-reference">
    `.impeccable/config.json` keys for hook, detector, live, update, and staleness.
  </Card>
  <Card title="Design hook" href="/design-hook">
    Provider hooks, quiet/disabled behavior, and intentional-finding workflow.
  </Card>
  <Card title="Project artifacts" href="/project-artifacts">
    PRODUCT.md / DESIGN.md discovery order and `.impeccable/` layout.
  </Card>
  <Card title="Live browser iteration" href="/live-mode">
    Live mode roots, config, and web-only constraints.
  </Card>
  <Card title="Doctor" href="/doctor">
    On-demand staleness and schema repair when boot checks are opted out.
  </Card>
  <Card title="CLI reference" href="/cli-reference">
    Install, update, detect, and skills commands that honor bundle and provider paths.
  </Card>
</CardGroup>
