Skip to content

Environment Variables — Reference

All environment variables read or written by amplihack during a launch (amplihack claude, amplihack copilot, amplihack codex, amplihack amplifier).

Contents


Variables set by amplihack

These variables are injected into every child process launched by amplihack. They are not inherited from the parent shell; they are built fresh on each invocation.


AMPLIHACK_AGENT_BINARY

Type: string Allowed values: claude | copilot | codex | amplifier (case-insensitive, exact match after trim) Default: copilot Set by: EnvBuilder::with_agent_binary() (as a back-compat read-through cache) Read by: amplihack_utils::agent_binary::resolve() (precedence step 1)

Identifies which CLI binary the current session should use when spawning new AI sessions. As of the workflow runtime-isolation contract, this variable is an explicit override and read-through cache, not the only routing source. The shared resolver consults:

  1. AMPLIHACK_AGENT_BINARY env var (explicit override; CI/testing/back-compat)
  2. $AMPLIHACK_RUNTIME_ROOT/launcher_context.json launcher field (canonical workflow runtime state)
  3. <repo>/.claude/runtime/launcher_context.json launcher field (legacy fallback only)
  4. Built-in default: copilot

The launcher continues to write this variable to subprocess environments so that external consumers (notably rysweet/amplihack-recipe-runner) that have not yet migrated to the file-based resolver continue to work. New code inside amplihack-rs should call amplihack_utils::agent_binary::resolve(&cwd) instead of reading the env var directly.

Validation

Values are normalized (trim, lowercase) and matched against the allowlist {claude, copilot, codex, amplifier}. Values that contain /, \, .., null bytes, whitespace, control characters, or exceed 32 bytes are rejected. On rejection the resolver emits a structured tracing::warn! and falls through to the next precedence source.

# Start a Copilot session (the new default)
amplihack copilot

# Inside hooks, recipe steps, sub-agents:
echo $AMPLIHACK_AGENT_BINARY
# copilot

# Explicit override (CI, testing, manual selection)
AMPLIHACK_AGENT_BINARY=claude amplihack recipe run smart-orchestrator -c task_description="..."

# Invalid values are rejected and the resolver falls through
AMPLIHACK_AGENT_BINARY="../bin/evil" amplihack copilot
# warn: rejected AMPLIHACK_AGENT_BINARY (failed allowlist); falling back to runtime launcher context

Why the precedence order

  • Env var first preserves the established escape hatch for CI/testing and lets external recipe-runner builds keep working unchanged.
  • Runtime-root launcher context second keeps durable workflow state outside the task worktree while preserving routing for descendants that inherit AMPLIHACK_RUNTIME_ROOT.
  • Legacy .claude/runtime fallback third preserves older repositories long enough to migrate without treating task-worktree runtime state as canonical.
  • copilot default last matches the project's current preferred runtime and removes the prior implicit claude assumption.

Why it exists: Recipe runner, hooks, and sub-agents are agent-agnostic and must call back into whatever tool the user actually launched. See Active Agent Binary for the full algorithm and Agent Binary Routing for the architectural rationale.

Python parity: Python skill scripts (amplifier-bundle/skills/pm-architect/scripts/agent_query.py, delegate_response.py) implement the same precedence and same allowlist; agent_query.py::detect_runtime() is the canonical Python entry point and is reused by delegate_response.py. The shell helper at amplifier-bundle/skills/migrate/scripts/migrate.sh re-implements the same algorithm with a case statement allowlist. The active binary is therefore consistent across Rust, Python, and shell code paths.

Existing claude users: repos that already have .claude/runtime/launcher_context.json with "launcher": "claude" continue to resolve to claude during migration — the legacy fallback wins over the new copilot default when runtime-root state and the env override are absent. New workflow code must write launcher context under AMPLIHACK_RUNTIME_ROOT, not under the task worktree.

Effect on startup self-update prompt: A non-empty AMPLIHACK_AGENT_BINARY is also recognised by the startup self-update prompt as a subprocess-safe signal — when the variable is set, the prompt is skipped and the skip-line amplihack: skipping update check (subprocess-safe / no TTY) is emitted to stderr. This means delegated agent invocations never block on the prompt, even at an interactive TTY. See Startup Self-Update Prompt — Subprocess-Safe Skip.


AMPLIHACK_ASSET_RESOLVER

Type: path Example: /home/alice/.local/bin/amplihack-asset-resolver Set by: EnvBuilder::with_asset_resolver()

Absolute path to the native bundle-asset resolver. Child processes can execute this binary with a single supported named asset or relative asset path argument, for example:

"$AMPLIHACK_ASSET_RESOLVER" amplifier-bundle/recipes/smart-orchestrator.yaml
"$AMPLIHACK_ASSET_RESOLVER" hooks-dir

That returns the resolved absolute path on stdout and exits non-zero on invalid input or missing assets. The standalone binary resolves assets through the same CLI resolver as amplihack resolve-bundle-asset.

The CLI library's Rust named-asset table is the shared source for the standalone binary's usage/listing output and the runtime helper mappings, so those consumers stay in sync with the command.

Resolution order (first match wins):

Priority Source
1 AMPLIHACK_ASSET_RESOLVER already set in environment
2 Sibling amplihack-asset-resolver next to the running amplihack executable
3 PATH lookup
4 ~/.local/bin/amplihack-asset-resolver
5 ~/.cargo/bin/amplihack-asset-resolver

If no binary is found, the variable is omitted. Callers that require native resolution should treat absence as a hard setup problem rather than silently degrading.

Why it exists: Python's resolve_bundle_asset.py was a hidden runtime dependency for recipes and helper scripts. Exposing a dedicated Rust binary makes asset lookup explicit, testable, and reusable by child tools without embedding Python-specific paths.


AMPLIHACK_HOME

Type: path Example: /home/alice/.amplihack Set by: EnvBuilder::with_amplihack_home()

The root directory where amplihack stores framework assets, hooks, runtime state, and helper scripts. Recipe runner uses this to locate .claude/tools/amplihack/ and related subdirectories without requiring hardcoded paths.

Resolution order (first match wins):

Priority Source Example result
1 AMPLIHACK_HOME already set in environment value is passed through unchanged
2 $HOME/.amplihack /home/alice/.amplihack
3 Directory containing the amplihack binary /usr/local/bin/../amplihack
— All above fail variable is not set (silent degradation)
# Override for a non-standard install location
export AMPLIHACK_HOME=/opt/amplihack
amplihack claude

# Verify the value a subprocess receives
AMPLIHACK_HOME=/opt/amplihack amplihack claude --print-env 2>&1 | grep AMPLIHACK_HOME
# AMPLIHACK_HOME=/opt/amplihack

Security note: The resolved path is validated to be absolute and must not contain .. path components. Paths that fail validation are silently dropped; a warning is emitted to the trace log.

Python parity: Corresponds to AMPLIHACK_HOME propagation in the Python launcher.


AMPLIHACK_GRAPH_DB_PATH

Type: path Example: /work/repo/.amplihack/graph_db Set by: EnvBuilder::with_project_graph_db() Read by: commands::memory::resolve_memory_graph_db_path()

Overrides the code-graph database path used by Rust memory operations in launched child processes. amplihack launch and the Rust recipe runner set it to the project-local .amplihack/graph_db so launched sessions, hooks, and native code-graph features operate on the same live store.

If this variable is absent, amplihack sets it to the project-local .amplihack/graph_db directory. The legacy AMPLIHACK_KUZU_DB_PATH override is accepted as an alias and translated to AMPLIHACK_GRAPH_DB_PATH in the child process environment.

# Effective child-process environment for a project rooted at /work/repo
AMPLIHACK_GRAPH_DB_PATH=/work/repo/.amplihack/graph_db amplihack claude

Why it exists: Launched sessions, hooks, and native code-graph features must all operate on the same project-local DB. Setting this variable ensures every subprocess resolves to the correct location without relying on filesystem detection heuristics.


AMPLIHACK_KUZU_DB_PATH (backward-compatible alias)

Legacy compatibility alias for AMPLIHACK_GRAPH_DB_PATH.

If AMPLIHACK_GRAPH_DB_PATH is unset, Rust still reads AMPLIHACK_KUZU_DB_PATH and also exports it for older child-process consumers. When both are present, AMPLIHACK_GRAPH_DB_PATH wins.

# Backward-compatible older configuration still works
AMPLIHACK_KUZU_DB_PATH=/work/repo/.amplihack/graph_db amplihack claude

The alias remains because the storage engine was originally named Kuzu (now rebranded to LadybugDB), but new automation should prefer AMPLIHACK_GRAPH_DB_PATH so the public surface stays backend-neutral.


AMPLIHACK_NONINTERACTIVE

Type: flag Values: 1 (non-interactive) — absence or any other value means interactive Read by: util::is_noninteractive() Set by: EnvBuilder::set_if(is_noninteractive(), "AMPLIHACK_NONINTERACTIVE", "1")

Signals that the process is running in a non-interactive environment. When set to 1, amplihack skips all interactive prompts and framework bootstrap guidance, preventing hangs in CI pipelines, pipes, and sandboxed environments.

# Run without interactive prompts (e.g. in CI)
AMPLIHACK_NONINTERACTIVE=1 amplihack claude --print 'Fix the lint errors'

# Pipe use also triggers non-interactive mode automatically (no TTY on stdin)
echo 'Summarize this file' | amplihack claude --print -

Detection logic:

Non-interactive mode is active when either condition is true:

  1. AMPLIHACK_NONINTERACTIVE=1 is set in the environment
  2. stdin is not a TTY (detected via std::io::IsTerminal)

Condition 2 covers pipe usage without requiring the caller to set the variable manually.

Effect on bootstrap: When non-interactive mode is detected, prepare_launcher() returns immediately without running check_required_tools() or ensure_framework_installed(). The assumption is that CI environments are pre-provisioned and that interactive guidance output would be noise.

Effect on update check: Non-interactive mode also suppresses the pre-launch npm update check. No npm subprocesses are spawned. This is equivalent to passing --skip-update-check on every invocation. See Manage Tool Update Notifications for details.

Effect on startup self-update prompt: A non-empty AMPLIHACK_NONINTERACTIVE (any value, not only "1") also skips the amplihack startup self-update prompt — the Update now? [y/N] (5s timeout): line is never printed and stdin is never read. A single skip-line amplihack: skipping update check (subprocess-safe / no TTY) is emitted to stderr. See Startup Self-Update Prompt — Subprocess-Safe Skip.

Propagation: Once detected, AMPLIHACK_NONINTERACTIVE=1 is written into the child process environment so that nested invocations (e.g. sub-agents spawned by hooks) also behave non-interactively.

Cross-language contract: Only the value "1" triggers non-interactive mode. The strings "true", "yes", "on", and "TRUE" are not recognised — this matches the Python launcher's behaviour.

Python parity: Corresponds to AMPLIHACK_NONINTERACTIVE check in amplihack/cli/launch.py (Python PRs #3103, #3066).


AMPLIHACK_SESSION_ID

Type: string Example: rs-1741872000-12345 Set by: EnvBuilder::with_amplihack_session_id()

A correlation ID for the current session. Used in log output and by the nesting detector to identify recursive amplihack invocations. Reused unchanged if already set in the environment (i.e. a nested invocation inherits the session ID of its parent).

Format: rs-<unix_seconds>-<pid>


AMPLIHACK_DEPTH

Type: integer string Default: 1 Set by: EnvBuilder::with_amplihack_session_id()

Nesting depth of the current invocation. The root invocation receives 1. Nested sessions (amplihack launched from within a Claude Code hook) inherit the value from the environment unchanged; the Python launcher increments it, but the Rust launcher propagates it as-is to match Python's observed behaviour for initial launches.


AMPLIHACK_RUST_RUNTIME

Type: flag Value: always 1 Set by: EnvBuilder::with_amplihack_vars()

Indicates the session was started by the Rust CLI rather than the Python launcher. Hooks and recipe scripts can use this to branch on runtime differences.

# In a hook script
if [ "$AMPLIHACK_RUST_RUNTIME" = "1" ]; then
  # Rust-specific code path
fi

AMPLIHACK_VERSION

Type: semver string Example: 0.3.1 Set by: EnvBuilder::with_amplihack_vars()

The version of the amplihack-cli binary that launched the session. Release builds use AMPLIHACK_RELEASE_VERSION when it was set at compile time; local developer builds fall back to CARGO_PKG_VERSION.


AMPLIHACK_RELEASE_VERSION

Type: semver string Example: 0.9.78 Set by: release build environment Read by: Rust compile-time option_env!("AMPLIHACK_RELEASE_VERSION")

Build-time override for the version embedded in released binaries. The release workflow sets this value while compiling so amplihack --version, doctor output, plugin manifests, hook context loading, and the runtime AMPLIHACK_VERSION child-process variable all report the release tag version.

Local builds normally leave this unset and use CARGO_PKG_VERSION.

AMPLIHACK_RELEASE_VERSION=0.9.78 cargo build --release --locked --bin amplihack
./target/release/amplihack --version

NODE_OPTIONS

Type: space-separated Node.js CLI flags Set by: launcher startup via memory_config.rs, then propagated by EnvBuilder::with_amplihack_vars()

amplihack now computes a smart --max-old-space-size=<mb> value at top-level launcher startup based on detected system RAM, persists the consent choice in ~/.amplihack/config, and displays the active choice on launch. The resolved value is then propagated through EnvBuilder.

When startup does not supply an explicit value, EnvBuilder still falls back to --max-old-space-size=32768, and if ambient NODE_OPTIONS already contains --max-old-space-size= it is preserved rather than duplicated.


Variables injected by recipe executor

These variables are read, set, or removed by the recipe execution path (amplihack recipe run) while launching recipe-runner-rs and running recipe steps. Heartbeat, snippet, and JSONL log variables are optional user configuration. See Recipe Executor Environment and Recipe Runner Logging for full details.


AMPLIHACK_STEP_TIMEOUT

Type: string (unsigned integer as text) Values: "0" (disable timeouts) | "600" (override to 600 seconds) | any non-negative integer Set by: amplihack recipe run --step-timeout <SECONDS>

Overrides the timeout_seconds value defined in individual recipe steps. When set, every step in the recipe uses this value instead of its YAML-defined timeout. A value of "0" disables step timeouts entirely, allowing steps to run indefinitely.

This variable is only present in the child environment when the user passes --step-timeout to amplihack recipe run. When the flag is omitted, YAML-defined timeout_seconds values apply as-is (though the default-workflow agent steps no longer define timeout_seconds).

# Override all step timeouts to 10 minutes
amplihack recipe run recipe.yaml --step-timeout 600
# Child process sees: AMPLIHACK_STEP_TIMEOUT=600

# Disable all step timeouts
amplihack recipe run recipe.yaml --step-timeout 0
# Child process sees: AMPLIHACK_STEP_TIMEOUT=0

# No override — YAML timeouts apply (agent steps have none by default)
amplihack recipe run recipe.yaml
# AMPLIHACK_STEP_TIMEOUT is NOT set in child environment

Why it exists: The default-workflow recipes no longer define timeout_seconds on agent steps, so agent steps run to completion without artificial time limits. This variable provides an opt-in escape hatch for CI environments that need wall-clock budgets. The env var approach is forward-compatible — recipe-runner-rs can adopt it independently without CLI changes.

Security note: The value is always a u64 rendered as a string. No shell metacharacters are possible. The CLI rejects non-numeric input at parse time via clap's type enforcement.


AMPLIHACK_NONINTERACTIVE (recipe runner subprocess)

Type: flag Value: 1 Set by: amplihack recipe run centralized subprocess environment

The recipe runner launch writes AMPLIHACK_NONINTERACTIVE=1 into the recipe-runner-rs child environment. This is stronger than top-level launcher propagation: the value is forced for recipe execution even when the parent process is interactive and the parent environment does not contain AMPLIHACK_NONINTERACTIVE.

This prevents nested recipe, hook, and agent subprocesses from asking for input or showing interactive update prompts while an automated workflow is already in progress.

# The recipe-runner child receives AMPLIHACK_NONINTERACTIVE=1.
env -u AMPLIHACK_NONINTERACTIVE \
  amplihack recipe run default-workflow \
  -c task_description="Update generated docs" \
  -c repo_path=.

AMPLIHACK_RECIPE_RUN_ID

Type: UUID string Set by: amplihack recipe run centralized subprocess environment Read by: recipe-runner-rs, recipe steps, child tools, and log consumers

Stable correlation identity for one amplihack recipe run invocation. The CLI generates this UUID before spawning recipe-runner-rs, injects it into the child environment, writes it to early and final amplihack.recipe.log_pointer stderr events, and includes it in final JSON/YAML results as run_id when a result can be produced.

amplihack recipe run default-workflow \
  -c task_description="Fix flaky retry tests" \
  -c repo_path=. \
  --format json > result.json 2> progress.log

jq -r '.run_id' result.json
grep '^amplihack\.recipe\.log_pointer ' progress.log

Treat this variable as read-only. Do not set it manually for normal use; the wrapper creates a fresh UUID for each invocation so concurrent runs do not share an identity.

See Recipe Run Correlation Reference for the pointer event schema and final result fields.


AMPLIHACK_RUNTIME_ROOT

Type: absolute path Set by: amplihack recipe run runtime-root resolver when absent; preserved when explicitly supplied by the caller Read by: launchers, recipe steps, workflow provenance logging, publish helpers, finalization helpers, and nested agents

Directory where workflow-generated runtime files are written. The runtime root keeps generated launcher state, provenance, logs, metrics, reflection output, locks, and child-agent runtime files outside the commit worktree. The top-level workflow establishes this value once and child workflows inherit it unchanged.

Resolution order:

Priority Source
1 Existing AMPLIHACK_RUNTIME_ROOT value
2 $XDG_RUNTIME_DIR/amplihack/runtime/<AMPLIHACK_RECIPE_RUN_ID>
3 /tmp/amplihack-runtime/<user>/<AMPLIHACK_RECIPE_RUN_ID>

The resolver creates these subdirectories with restrictive owner-only permissions where the platform supports them:

locks/
logs/
metrics/
provenance/
reflection/
# Override the runtime root for one recipe run.
AMPLIHACK_RUNTIME_ROOT=/var/tmp/amplihack-runtime/login-fix \
  amplihack recipe run default-workflow \
  -c task_description="Fix login flake" \
  -c repo_path=.

Use a private path outside the repository worktree. Do not point this variable at .claude/runtime, worktrees/, target/, or another generated directory inside the active task worktree.

See Workflow Runtime Artifacts Reference for the full runtime-root, cleanup, and lifecycle preflight contract.


AMPLIHACK_RESULT_SINK

Type: absolute path Set by: the orchestration runner (run_delivered_command) when a caller opts in via RunOptions.result_sink Read by: the spawned agent / recipe step

Names the clean result channel file for a step. When present and non-empty, the child should write its final semantic answer (free text — JSON not required) to this path in addition to whatever it prints on stdout. After the child exits, the runner reads this file verbatim into ProcessResult.result, giving consumers the answer without scraping ANSI / tracing / banner noise out of stdout.

Key behaviours:

  • Opt-in only. Exported only when RunOptions.result_sink is Some. Absent ⇒ legacy stdout-only capture (result == None).
  • No stale inheritance. When the caller does not opt in, the runner env_removes any inherited AMPLIHACK_RESULT_SINK so an ancestor value can never silently redirect capture.
  • Runner-owned path. The path is allocated by the runner under the run's runtime directory with owner-only permissions; it is never taken from untrusted recipe context.
  • Verbatim. The runner performs no ANSI-strip, trim, newline-normalize, or JSON parse. Oversize or non-UTF-8 contents yield result == None (fallback to stdout).
# Child-side idiom: honour the channel when the runner provides it.
if [ -n "${AMPLIHACK_RESULT_SINK:-}" ]; then
  printf '%s' "$FINAL_ANSWER" > "$AMPLIHACK_RESULT_SINK"
fi

See the Clean Result Channel Reference for the full contract, API, verbatim guarantee, and migration example.


CLAUDECODE (recipe runner subprocess)

Type: removed environment variable Removed by: amplihack recipe run centralized subprocess environment

The recipe runner launch explicitly removes CLAUDECODE from the recipe-runner-rs child environment. The variable is Claude-Code-specific session state, not a portable amplihack routing signal. Leaving it set in a nested recipe can make downstream agents believe they are running inside the original Claude Code host even when the active launcher is Copilot, Codex, or Amplifier.

Use AMPLIHACK_AGENT_BINARY for runtime routing. It is validated, propagated, and documented as the active agent selector.

# The parent value is ignored for the recipe-runner child.
CLAUDECODE=1 amplihack recipe run smart-orchestrator \
  -c task_description="Review the open PR" \
  -c repo_path=.

AMPLIHACK_RECIPE_HEARTBEAT_INTERVAL_SECONDS

Type: string (unsigned integer as text) Values: "0" (disable heartbeats) | "60" (default) | any non-negative integer Read by: recipe-runner-rs

Controls how often long-running recipe, agent, subprocess, and nested-recipe steps emit heartbeat lines to stderr. The interval is rate-limited per active step. Short steps that complete before one interval do not emit heartbeats.

# Emit a heartbeat every 15 seconds while debugging a long-running agent step
AMPLIHACK_RECIPE_HEARTBEAT_INTERVAL_SECONDS=15 \
amplihack recipe run default-workflow \
  -c task_description="Debug hanging test generation"

# Disable heartbeat lines while preserving start/completion/failure progress
AMPLIHACK_RECIPE_HEARTBEAT_INTERVAL_SECONDS=0 \
amplihack recipe run default-workflow \
  -c task_description="Run with minimal progress"

Equivalent config key: recipe.heartbeat_interval_seconds in ~/.amplihack/config.


AMPLIHACK_RECIPE_SNIPPET_LINES

Type: string (unsigned integer as text) Default: 20 Read by: recipe-runner-rs

Maximum number of recent lines retained per active child source and stream. Older lines are dropped from the rolling buffer. This bound applies to snippets printed in failure diagnostics, included in JSON results, and written to JSONL logs.

AMPLIHACK_RECIPE_SNIPPET_LINES=60 \
amplihack recipe run default-workflow \
  -c task_description="Diagnose compiler failure" \
  --format json > result.json

Equivalent config key: recipe.snippet_lines in ~/.amplihack/config.


AMPLIHACK_RECIPE_SNIPPET_BYTES

Type: string (unsigned integer as text) Default: 8192 Read by: recipe-runner-rs

Maximum bytes retained per active child source and stream. This byte bound is enforced together with AMPLIHACK_RECIPE_SNIPPET_LINES; whichever limit is hit first controls truncation.

AMPLIHACK_RECIPE_SNIPPET_BYTES=32768 \
amplihack recipe run default-workflow \
  -c task_description="Diagnose noisy subprocess output" \
  --format json > result.json

Equivalent config key: recipe.snippet_bytes in ~/.amplihack/config.


AMPLIHACK_RECIPE_TRANSIENT_MAX_ATTEMPTS

Type: string (unsigned integer as text) Default: 3 Read by: amplihack recipe run

Total attempts — the first try plus retries — made when a recipe run fails with an unambiguously transient transport fault (HTTP 529/503/502/500/429, a reset connection, a socket timeout). Nothing else is retried: a failing test, a missing binary, or a policy refusal is terminal on the first attempt.

Values above 10 are clamped to 10; 1 disables the retry entirely. Unset, empty, 0, and unparsable values fall back to the default.

AMPLIHACK_RECIPE_TRANSIENT_MAX_ATTEMPTS=5 \
amplihack recipe run default-workflow \
  -c task_description="Long run over a flaky endpoint"

See Transient Failure Retry (issue #1267) for the classification rules and the greppable markers each decision writes.


AMPLIHACK_RECIPE_TRANSIENT_BUDGET_SECS

Type: string (unsigned integer as text, seconds) Default: 300 Read by: amplihack recipe run

Budget for the total time spent waiting on backoff across all transient-transport retries in one recipe run. Backoff delays start at 10s and double with equal jitter, clamped so a wait never overshoots the remaining budget. Whichever bound is reached first — this one or AMPLIHACK_RECIPE_TRANSIENT_MAX_ATTEMPTS — ends the retry with a terminal error naming the class.

This is deliberately not wall-clock time since the run started. A 529 typically arrives hours into a long workstream; a budget measured from the start would already be exhausted at the first failure, and no retry would ever be attempted. Only the idle waiting is charged against this budget, so the duration of the work itself is irrelevant to it.

AMPLIHACK_RECIPE_TRANSIENT_BUDGET_SECS=900 \
amplihack recipe run default-workflow \
  -c task_description="Tolerate a longer provider outage"

AMPLIHACK_RECIPE_LOG_JSONL

Type: path Read by: recipe-runner-rs

When set, writes structured JSONL recipe events to the specified file. Events include the recipe run_id, step lifecycle transitions, heartbeats, output snippets, and failure context. Human-readable progress still goes to stderr.

AMPLIHACK_RECIPE_LOG_JSONL=/tmp/default-workflow.jsonl \
amplihack recipe run default-workflow \
  -c task_description="Add retry budget metrics"

jq 'select(.type == "heartbeat")' /tmp/default-workflow.jsonl

Equivalent config key: recipe.log_jsonl in ~/.amplihack/config.


NONINTERACTIVE

Type: string Value: 1 Set by: Recipe executor, execute_shell_step()

Signals to general-purpose tools that they should not attempt interactive prompts. Not specific to any one tool — serves as a generic non-interactive flag.


DEBIAN_FRONTEND

Type: string Value: noninteractive Set by: Recipe executor, execute_shell_step()

Suppresses interactive prompts from dpkg and apt. Standard Debian/Ubuntu convention for headless package management.


CI (recipe context)

Type: string Value: true Set by: Recipe executor, execute_shell_step()

Signals CI-like behavior to npm, yarn, pip, and other tools that check this variable before prompting. Note: this is set by the recipe executor for all recipe steps, independent of whether the top-level process is actually running in a CI system.


Context-derived recipe variables

Type: string (one variable per recipe context key) Examples: TASK_DESCRIPTION, REPO_PATH, ISSUE_NUMBER, BRANCH_NAME Set by: amplihack recipe run, context environment export (lowest precedence)

Every recipe context variable is exported to the recipe-runner-rs subprocess — and therefore to every shell and nested sub-recipe step — as an environment variable whose name is the ASCII-uppercased context key. For example, task_description becomes TASK_DESCRIPTION and repo_path becomes REPO_PATH. Bash steps can read these directly, including under set -u.

amplihack recipe run env-aware-recipe \
  -c task_description="Add validation" \
  -c repo_path=.
# A bash step sees: TASK_DESCRIPTION=Add validation, REPO_PATH=.

The exact set of names depends entirely on the recipe context. Rules:

  • Names must be valid shell identifiers (^[A-Z_][A-Z0-9_]*$); keys that do not qualify after uppercasing are skipped with a name-only WARN.
  • Reserved and dangerous names are never exported — including PATH, HOME, SHELL, IFS, the LD_*/DYLD_* loader variables, BASH_ENV, ENV, PS4, PROMPT_COMMAND, SHELLOPTS, BASHOPTS, PYTHONPATH, NODE_OPTIONS, PERL5OPT, RUBYOPT, and any name beginning with AMPLIHACK_.
  • Context export is applied at the lowest precedence, so it can never override the AMPLIHACK_* variables or AMPLIHACK_RECIPE_RUN_ID documented above.

See Recipe Context Environment Export for the full contract, denylist, and security model.


AMPLIHACK_CONTEXT_ENV_BUDGET_BYTES

Type: string (unsigned integer as text) Values: any non-negative integer (0 = essentials only) | unset (adaptive default) Read by: amplihack recipe run, context environment export

Overrides the aggregate byte budget for mirroring recipe context into the recipe-runner-rs environment. The context mirror is inherited by every bash step, so an unbounded mirror could push the cumulative environment past the kernel's ARG_MAX and make a late step fail with Argument list too long (os error 7) (issue #1023). By default the budget is derived adaptively at spawn time from sysconf(_SC_ARG_MAX) minus the inherited environment and a reservation; this variable pins it explicitly.

  • When unset, the budget is derived adaptively (recommended).
  • When set to a valid usize, that exact byte budget is used and takes precedence over the derived value.
  • 0 is valid and means "mirror only the essential keys" (task_description, repo_path, existing_branch, should_*).
  • An invalid value (non-numeric, negative, overflowing) is ignored with a name-only WARN reason=invalid_env_budget_override, and the derived budget is used — the run is never aborted for a bad override.

Essential keys are always exported regardless of the budget. Non-essential keys are filled smallest-first until the budget is exhausted; any key dropped from the mirror is still delivered to the runner via the --context-file path for {{placeholder}} substitution.

# Pin the mirror budget to 256 KB
AMPLIHACK_CONTEXT_ENV_BUDGET_BYTES=262144 \
amplihack recipe run default-workflow \
  -c task_description="Large multi-file campaign"

# Mirror only essential keys (drop all non-essential mirroring)
AMPLIHACK_CONTEXT_ENV_BUDGET_BYTES=0 \
amplihack recipe run default-workflow \
  -c task_description="Minimal env footprint"

See Recipe Context Environment Export → Aggregate environment budget for the derivation, essential-key set, and smallest-first fill.


Variables read by amplihack

These variables influence amplihack's behaviour but are not set by it.


External LiteLLM gateway variables

These three variables form the complete opt-in external LiteLLM configuration. If any one is present, all three must be valid. Invalid, empty, or partial configuration fails before an agent process is spawned.

Variable Type and default Purpose
AMPLIHACK_LITELLM_ENDPOINT URL; unset Gateway base URL. HTTPS is required except for literal loopback development URLs.
AMPLIHACK_LITELLM_API_KEY secret string; unset Restricted LiteLLM virtual key. It is never serialized or placed in child command arguments. Do not use an administrative master key.
AMPLIHACK_LITELLM_MODEL model name; unset Required for every supported launcher; selects the gateway model alias.

LiteLLM and PostgreSQL own usage, spend, budgets, and rate limits. Amplihack does not implement process-local gateway accounting or controls.

When the selected launcher is Claude Code, amplihack sets CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1; users do not need to set that variable. External Claude Code routing requires the exact claude executable to report semantic version 2.1.247. Amplihack probes it before checkout, auto-mode staging, launch setup, session tracking, or Docker operations and rejects missing executables, failed probes, malformed or unknown output, prereleases, and all other versions. The probe will receive neither AMPLIHACK_LITELLM_API_KEY nor direct provider credentials.

The Docker launcher rejects loopback gateway endpoints so normal container network isolation remains intact. Use an HTTPS gateway reachable from the container, or run the agent outside Docker for a host-loopback development gateway.

See the external LiteLLM boundary for the routing and ownership contract.


AMPLIHACK_MEMORY_BACKEND

Type: string Values: sqlite | graph-db | kuzu Read by: resolve_memory_backend_preference(), resolve_transfer_backend_choice(), resolve_backend_with_autodetect()

Selects the memory storage backend for all memory commands: memory tree, memory clean, memory export, and memory import. When unset, the backend is chosen by probing the filesystem in priority order:

  1. AMPLIHACK_GRAPH_DB_PATH path exists on disk → graph-db
  2. ~/.amplihack/memory_graph.db exists → graph-db
  3. Neither exists → sqlite (default for new installs)
# Permanently opt into SQLite (add to shell profile)
export AMPLIHACK_MEMORY_BACKEND=sqlite

# Single-invocation override
AMPLIHACK_MEMORY_BACKEND=graph-db amplihack memory tree

# Legacy kuzu alias still works (backward-compatible)
AMPLIHACK_MEMORY_BACKEND=kuzu amplihack memory tree

Values outside the allowlist ['sqlite', 'kuzu', 'graph-db'] produce a visible warning to stderr and fall back to the graph-db backend. There is no silent acceptance of unrecognised values.

Why it exists: New installs default to SQLite (no native library required). Existing installs with a populated memory_graph.db continue to use graph-db automatically. This variable lets users and CI pipelines opt in or out of either backend without modifying on-disk state.

See Memory Backend Reference for the complete backend configuration reference.


HOME

Required: yes (for most operations)

Standard Unix home directory. Used to resolve ~/.amplihack, ~/.npm-global, and shell profile paths.


AMPLIHACK_DEFAULT_MODEL

Type: string Default: claude-opus-5[1m] (a concrete model id, not an alias; issue #1421). An empty or whitespace-only value passes no --model. Used by: configured_default_model() in commands/launch/command.rs

Sets the --model amplihack passes to Claude-compatible tools: claude, rusty, rustyclawd and amplifier. amplihack copilot and amplihack codex ignore it.

AMPLIHACK_DEFAULT_MODEL What amplihack adds
unset, or not valid UTF-8 --model claude-opus-5[1m]
a model id --model with that id, trimmed of surrounding whitespace; a dotted Claude id is rewritten to hyphens
empty or whitespace-only nothing; the tool picks its own default

Because amplihack passes --model unless this variable is empty, the "model" in ~/.claude/settings.json only takes effect when you set AMPLIHACK_DEFAULT_MODEL= (empty).

When any External LiteLLM gateway variable is set, this variable is not read: the model is the required AMPLIHACK_LITELLM_MODEL, passed unchanged. It is never normalised, because the gateway routes on the exact name.

Why the default is a concrete id: an alias such as opus[1m] is resolved by the tool, and amplihack does not control the tool's version. On one install that alias resolved to the retired claude-opus-4-1-20250805, and every agent step failed with a 404 naming a model the user had never chosen. A concrete id either works or fails naming the exact string amplihack sent.

# Pin a model for every Claude launch
AMPLIHACK_DEFAULT_MODEL=claude-sonnet-4-5 amplihack claude
# amplihack adds: --model claude-sonnet-4-5

# Let Claude Code (and ~/.claude/settings.json) choose
AMPLIHACK_DEFAULT_MODEL= amplihack claude
# amplihack adds no --model

Whenever amplihack adds --model, it prints one line to stderr naming the model and where it came from, so a later "model not found" error can be traced back to it:

amplihack: passing `--model claude-sonnet-4-5` to `claude` (from AMPLIHACK_DEFAULT_MODEL). Set AMPLIHACK_DEFAULT_MODEL to override it, or to an empty value to let claude choose its own default model.

With the variable unset or not valid UTF-8, the source reads amplihack's built-in default.

On the LiteLLM gateway path the line names AMPLIHACK_LITELLM_MODEL, both as the source and as the variable to change, because this variable is not read there:

amplihack: passing `--model gateway-model` to `claude` (from AMPLIHACK_LITELLM_MODEL). Set AMPLIHACK_LITELLM_MODEL to change it. AMPLIHACK_DEFAULT_MODEL is not read while a LiteLLM gateway variable is set.

Dotted Claude model ids

GitHub Copilot CLI writes Claude model ids with a dot (claude-opus-5.5). Claude model ids use hyphens (claude-opus-5-5; see Anthropic's models overview), and the variable is only read for Claude-compatible launches. So amplihack rewrites a dotted Claude id before passing it. Issue #1527 records what Claude Code did with the dotted form when it was reported; this page does not repeat it, because it depends on the Claude Code version.

AMPLIHACK_DEFAULT_MODEL='claude-opus-5.5[1m]' amplihack claude
# amplihack adds: --model claude-opus-5-5[1m]

The stderr line names both spellings:

amplihack: passing `--model claude-opus-5-5[1m]` to `claude` (from AMPLIHACK_DEFAULT_MODEL, normalised from `claude-opus-5.5[1m]`: Claude model ids use hyphens, not dots). Set AMPLIHACK_DEFAULT_MODEL to override it, or to an empty value to let claude choose its own default model.

The rewrite applies only to a value (after trimming) of exactly the form claude-<family>-<major>.<minor><suffix>, where:

  • <family> is one or more lowercase ASCII letters
  • <major> and <minor> are one or more ASCII digits
  • <suffix> is empty or starts with [ or -

Only the dot between <major> and <minor> changes, to a hyphen. The suffix is copied as typed. Matching is case-sensitive and exact, with no aliases and no fuzzy matching, so the result always names the same model you set.

Rewritten What amplihack adds
claude-opus-5.5 --model claude-opus-5-5
claude-sonnet-4.5 --model claude-sonnet-4-5
claude-opus-5.5[1m] --model claude-opus-5-5[1m]
claude-opus-4.1-20250805 --model claude-opus-4-1-20250805

Any other value is passed as-is:

Passed as-is Why
claude-opus-5-5, claude-opus-5-5[1m] already hyphenated
claude-opus-5[1m] no minor version (this is the built-in default)
opus[1m], sonnet aliases, not full ids
gpt-5.1, gemini-2.5-pro not Claude ids
claude-3.5-sonnet the version comes before the family
claude-opus-5.5.1 the suffix starts with ., not [ or -
Claude-Opus-5.5 not lowercase

Only the --model argument is rewritten. The launched tool inherits AMPLIHACK_DEFAULT_MODEL exactly as you set it. A nested amplihack launch reads it again and produces the same rewrite, unless that launch has an explicit --model or uses the LiteLLM gateway.

Explicit --model

A --model <id> or --model=<id> on the amplihack command line overrides this variable. It is forwarded exactly as typed and is never rewritten, even when dotted.

When an explicit value is a dotted Claude id of the form described above, amplihack forwards it unchanged and prints one warning to stderr naming the hyphenated spelling. The launched tool may not report the problem itself (issue #1527), so without this line the launch could give no sign of it:

amplihack: warning: passing `--model claude-opus-5.5` to `claude` as typed, but Claude model ids use hyphens, not dots. Use `--model claude-opus-5-5`.

The warning gives only the spelling, which amplihack can vouch for. It does not describe what the launched tool does with the dotted id, because that depends on the tool and its version.

There is no warning for any other explicit value, and none in these cases:

  • amplihack copilot and amplihack codex. The dotted spelling is GitHub Copilot CLI's own, so amplihack copilot --model claude-opus-4.5 is correct as typed.
  • The LiteLLM gateway path, because the gateway routes on the exact name and a dot in it may be correct.

Neither the rewrite nor the warning looks at ANTHROPIC_BASE_URL, so both apply whatever endpoint the launched tool talks to. If yours is a proxy that serves the dotted spelling, pass it as an explicit --model. amplihack forwards that as typed, and you can ignore the warning.

# The explicit --model wins over the variable and reaches claude unchanged
AMPLIHACK_DEFAULT_MODEL='claude-opus-5.5[1m]' amplihack claude --model claude-sonnet-4-5
# claude receives: --model claude-sonnet-4-5

# Forwarded unchanged, with the warning above
amplihack claude --model claude-opus-5.5

# Works
amplihack claude --model 'claude-opus-5-5[1m]'

For the other flags amplihack adds to the launch command, such as --dangerously-skip-permissions, and the order of the assembled command line, see Launch Flag Injection.


AMPLIHACK_COPILOT_NO_REMOTE

Type: flag Values: 1 suppresses --remote injection; absence or any other value keeps the default Used by: should_inject_copilot_remote() in commands/launch/command.rs

When amplihack copilot launches the Copilot CLI, it injects --remote by default to offload compute to GitHub's cloud. Set this variable to 1 to suppress the injection and run Copilot locally.

# Disable remote mode
AMPLIHACK_COPILOT_NO_REMOTE=1 amplihack copilot
# Copilot starts without --remote

Users can also pass --no-remote directly as an extra arg; the launcher detects this and skips injection automatically.


AMPLIHACK_ENABLE_BLARIFY

Type: flag Values: 1 enables launcher-side code-indexing checks; absence or any other value disables them Read by: commands::launch::should_prompt_blarify_indexing()

Opt-in gate for launcher-side code indexing. When set to 1 for amplihack claude, the Rust launcher checks whether code-graph artifacts are missing or stale, and whether the project-local .amplihack/graph_db store already exists, then either prompts or follows AMPLIHACK_BLARIFY_MODE if that mode is set.

Without AMPLIHACK_BLARIFY_MODE, interactive launches offer to either:

  • import an existing fresh .amplihack/blarify.json via amplihack index-code when the code-graph DB is missing, or
  • generate fresh native SCIP artifacts via amplihack index-scip when the existing artifact is stale or no fresh import input exists.

If the variable is unset, the launcher skips all code-indexing checks and proceeds directly to the target AI tool.

# Enable launcher-side code indexing prompts for Claude launches
AMPLIHACK_ENABLE_BLARIFY=1 amplihack claude

# Generate native SCIP artifacts manually instead of waiting for the prompt
amplihack index-scip --project-path .

Artifact locations:

  • .amplihack/blarify.json — LadybugDB import input for amplihack index-code
  • .amplihack/indexes/<language>.scip — per-language native SCIP artifacts from amplihack index-scip
  • .amplihack/graph_db — native code-graph store populated by index-code or index-scip

Why it exists: Code-graph indexing is computationally expensive and should not run on every launch. This flag keeps the behavior opt-in so only projects that benefit from code-graph enrichment pay the cost.


AMPLIHACK_BLARIFY_MODE

Type: string Values: skip | sync | background Read by: commands::launch::blarify_mode()

Controls how launcher-side code indexing behaves once AMPLIHACK_ENABLE_BLARIFY=1 has opted the project in.

  • skip — suppress indexing work for this launch
  • sync — run indexing in the foreground before launching Claude
  • background — start indexing in the background and continue launching Claude immediately

If the variable is unset or has any other value, interactive launches fall back to the prompt flow and non-interactive launches do nothing.

# Always skip indexing for this launch
AMPLIHACK_ENABLE_BLARIFY=1 AMPLIHACK_BLARIFY_MODE=skip amplihack claude

# Force synchronous indexing before Claude starts
AMPLIHACK_ENABLE_BLARIFY=1 AMPLIHACK_BLARIFY_MODE=sync amplihack claude

# Allow non-interactive launches to queue background indexing
AMPLIHACK_ENABLE_BLARIFY=1 AMPLIHACK_BLARIFY_MODE=background amplihack claude --print 'summarize src'

Why it exists: Python session-start integration already used a skip / sync / background contract. Reintroducing the same lifecycle knob in Rust lets automated and non-interactive launches opt into indexing policy without waiting on a TTY prompt.


AMPLIHACK_NO_UPDATE_CHECK

Type: flag Values: 1 (skip update check) — absence or any other value means check is enabled Used by: update::should_skip_update_check(), update::classify_skip_reason(), freshness::skip_freshness_checks(). Also set to 1 on self-update subprocesses by update::build_install_command / update::install as a recursion guard.

Permanently disables the update-check paths for every amplihack invocation:

  1. The pre-launch npm tool update check (notice for claude, copilot, codex package versions).
  2. The startup self-update prompt (Update now? [y/N] (5s timeout): for the amplihack binary itself).
  3. The bundle freshness check (skip_freshness_checks), so the same opt-out silences freshness warnings alongside the update checks.

Unlike AMPLIHACK_NONINTERACTIVE, this variable suppresses only the update checks and has no effect on bootstrap prompts or interactive behaviour. Unlike the subprocess-safe skip signals (CI, AMPLIHACK_AGENT_BINARY, AMPLIHACK_NONINTERACTIVE, --subprocess-safe, non-TTY stdin), this variable does not emit the amplihack: skipping update check (subprocess-safe / no TTY) skip-line on stderr — the suppression is silent. Use this when you want the pre-#625 silent-skip experience.

# Add to shell profile for a permanent per-user opt-out
export AMPLIHACK_NO_UPDATE_CHECK=1

Equivalent to passing --skip-update-check on every invocation, but without requiring the flag to be typed or aliased.

When to prefer this over AMPLIHACK_NONINTERACTIVE: Use AMPLIHACK_NO_UPDATE_CHECK=1 when you want to silence the update banner on a developer workstation while keeping interactive bootstrap prompts active. Use AMPLIHACK_NONINTERACTIVE=1 in CI environments where all interactive output should be suppressed.


AMPLIHACK_PARITY_TEST

Type: flag Values: 1 (parity-test mode active) — absence or any other value has no effect Used by: update::should_skip_update_check(), update::classify_skip_reason()

Suppresses both update-check paths (pre-launch npm tool notice and startup self-update prompt) without enabling full non-interactive mode. This is useful for automation that compares command output against a known baseline, where update-banner stderr output would create spurious differences. Suppression is silent — the amplihack: skipping update check (subprocess-safe / no TTY) skip-line introduced by issue #625 is not emitted, preserving byte-identical stderr against pre-#625 baselines.

AMPLIHACK_PARITY_TEST=1 amplihack claude --print 'run tests'

Custom automation scripts that compare amplihack output against a known baseline should also set this variable:

#!/usr/bin/env bash
# my-output-capture.sh
export AMPLIHACK_PARITY_TEST=1
actual=$(amplihack mode detect 2>&1)
expected="local"
[[ "$actual" == "$expected" ]] || { echo "FAIL: got $actual"; exit 1; }

Isolation contract: AMPLIHACK_PARITY_TEST=1 suppresses exactly one behaviour: the update check. It does not propagate into the child tool process, does not affect bootstrap logic, and does not change exit codes or stdout output.


AMPLIHACK_SKIP_AUTO_INSTALL

Type: flag Values: any non-empty value (suppresses self-heal) — absence or empty string means the check runs Used by: self_heal::ensure_assets_match_binary_version (via env_bypass_set)

Suppresses the startup-time self-heal check that runs before every amplihack command dispatch. With the bypass active, amplihack does not compare crate::VERSION to ~/.amplihack/.installed-version and does not auto-run install when the stamp is missing or stale. Explicit amplihack install invocations are unaffected.

When to set it:

  • CI pipelines that pre-stage assets in a setup phase and then run many amplihack commands without wanting the binary to mutate ~/.amplihack mid-run.
  • Unit/integration tests that stub out the framework asset tree and need to guarantee the binary will not overwrite it on first launch.
  • Sandboxed parity test harnesses that already manage the ~/.amplihack layout deterministically.
# CI: stage once, then run many commands without per-launch self-heal
amplihack install
export AMPLIHACK_SKIP_AUTO_INSTALL=1
amplihack claude --print 'run tests'
amplihack copilot --print 'run tests'

Truthiness: any non-empty value triggers the bypass (1, true, yes, please-skip, etc.). An empty string (AMPLIHACK_SKIP_AUTO_INSTALL="") is treated as unset and the check still runs.

Diagnostic on skip-with-mismatch: when the bypass is active and the stamp does not match crate::VERSION, amplihack emits one line on stderr before dispatch:

amplihack: self-heal skipped (AMPLIHACK_SKIP_AUTO_INSTALL set); stamp=<old> current=<new>

This makes the "stale assets, intentionally" state visible in CI logs. Matching versions produce no output.

What it does not do:

  • Does not affect the existing update::post_install hook fired by amplihack update.
  • Does not propagate into child tool processes (claude, copilot, etc.) — inherited only because it is a normal env var, not because amplihack re-exports it.
  • Does not change exit codes, stdout output, or any other behaviour besides the self-heal decision.

See: Self-Heal: Auto-Restage Framework Assets.


AMPLIHACK_SKIP_MMDC

Type: flag Values: any non-empty value (skips the attempt) — absence or empty string means the step runs Used by: install::mermaid_cli::ensure_mermaid_cli (during amplihack install)

Disables the best-effort Mermaid CLI provisioning step in amplihack install. When set to any non-empty value, the installer does not probe for mmdc, does not probe for npm, and does not run npm install -g @mermaid-js/mermaid-cli. The step resolves to SkippedByEnv and prints one informational line. amplihack install otherwise proceeds normally — this step is optional and never gates a successful install.

When to set it:

  • Offline / air-gapped installs that must not reach npm or download the puppeteer/Chromium payload (hundreds of MB).
  • Minimal containers or CI where local mermaid rendering is unnecessary and the pr-guide mermaid.ink fallback (or native Azure DevOps rendering) is acceptable.
  • Deterministic test harnesses that manage tool availability explicitly.
# Skip the best-effort mermaid CLI install entirely
AMPLIHACK_SKIP_MMDC=1 amplihack install

Truthiness: any non-empty value triggers the skip (1, true, yes, etc.). An empty string (AMPLIHACK_SKIP_MMDC="") is treated as unset and the step runs. This matches the presence-flag convention used by AMPLIHACK_SKIP_AUTO_INSTALL.

See: Best-Effort Mermaid CLI Provisioning.


AMPLIHACK_TEST_FAKE_LATEST_VERSION

Type: version tag string (test-only) Values: any version tag accepted by update::network::normalize_tag (e.g. 99.99.99, v0.9.3); empty string is treated as unset. Used by: update::network::fetch_latest_release()

Test-only short-circuit for the GitHub release lookup performed by the startup self-update prompt. When set non-empty, fetch_latest_release returns a synthetic UpdateRelease with the supplied tag and no network call is made.

The synthetic release uses an asset_url on the allowlisted github.com host and checksum_url=None, so any download path remains gated by the existing URL allowlist and SHA-256 verification — the variable cannot be used to redirect real downloads or bypass artifact verification. It exists exclusively to drive the prompt code path deterministically from the integration test suite at bins/amplihack/tests/issue_625_update_prompt_subprocess_safe.rs.

# Force a "newer release available" outcome without a network call
AMPLIHACK_TEST_FAKE_LATEST_VERSION=99.99.99 amplihack copilot --help

Production deployments should not set this variable. It is documented for completeness only.

See: Startup Self-Update Prompt — Subprocess-Safe Skip.


CI

Type: flag (presence-based) Values: any non-empty value (1, true, yes, anything) — empty string or absence has no effect. Used by: update::classify_skip_reason()

Conventional CI-runner marker recognised by amplihack as a subprocess-safe signal. When set non-empty, the startup self-update prompt is skipped — the Update now? [y/N] (5s timeout): line is never printed and stdin is never read. A single skip-line amplihack: skipping update check (subprocess-safe / no TTY) is emitted to stderr.

GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite, and most other CI runners set CI=true automatically, so this signal usually fires without any explicit configuration.

# .github/workflows/agent.yml — CI=true is set automatically by the runner.
- run: amplihack copilot -p "Run the test suite"

Empty-string semantics: CI="" does not trigger skip. Only non-empty values are recognised — matching the convention used by commands::launch::command::resolve_subprocess_safe.

No effect on the npm pre-launch tool notice. That notice is non-blocking and is suppressed by AMPLIHACK_NONINTERACTIVE / AMPLIHACK_NO_UPDATE_CHECK instead. See Manage Tool Update Notifications.

No effect on --subprocess-safe argv injection on the copilot subcommand. That feature uses its own resolver (see COPILOT_SUBPROCESS_SAFE.md); CI is one of its signals as well, but the two paths are independent.

See: Startup Self-Update Prompt — Subprocess-Safe Skip.


UV_TOOL_BIN_DIR

Type: path Used by: bootstrap.rs when installing amplifier

Override the directory where uv tool install places the amplifier binary. Defaults to ~/.local/bin.


IS_SANDBOX

Type: 1 to allow; 0 to refuse Used by: every claude --dangerously-skip-permissions amplihack starts (amplihack claude, recipe agent steps, the orchestration runner, the fleet reasoner)

Claude Code refuses --dangerously-skip-permissions as root unless IS_SANDBOX=1 is set. It accepts only the exact value 1. When amplihack runs as root:

  • IS_SANDBOX=1, or a truthy CLAUDE_CODE_BUBBLEWRAP, is passed through.
  • yes, true, on and y are passed to claude as 1.
  • 0 or any other value is never overridden, and amplihack fails before starting claude.
  • Unset in a detected container (CLAUDE_CODE_REMOTE=true, /.dockerenv, /run/.containerenv, or a container runtime in /proc/1/cgroup; the marker files count only outside WSL, which includes Docker Desktop containers on Windows, and only when / is PID 1's root, so not in a chroot): amplihack sets IS_SANDBOX=1 on the claude child only and prints a one-line notice on stderr.
  • Unset with no container detected: amplihack fails before starting claude.

Not root: ignored. See Agent steps fail as root.


AMPLIHACK_TOPIC_NAME

Type: string Values: any topic name — absence falls back to hive-graph Used by: commands::hive_haymaker::run_hive_feed, commands::hive_haymaker::run_hive_eval

Overrides the default hive event-bus topic name used by the hive feed and hive eval commands. An explicit --topic CLI argument takes precedence; when no argument is given, AMPLIHACK_TOPIC_NAME is consulted, and if it is unset the topic defaults to hive-graph.

export AMPLIHACK_TOPIC_NAME=my-hive
amplihack hive feed --deployment-id demo

AMPLIHACK_PROJECT_ID

Type: string Values: any project id — absence falls back to amplihack Used by: amplihack_memory::cli_memory::learning

Sets the project id recorded on session-learning memory records. When unset, the project id defaults to amplihack. Use this to segregate learned memories by project.

export AMPLIHACK_PROJECT_ID=my-project

Signal channel variables

These variables control the optional Signal channel (feature signal). They are read only when the channel is active and, unlike the launcher variables above, are documented authoritatively — with their TOML equivalents — in the Signal channel reference. The two variables below govern group strategy. The channel's default is per-session groups: every amplihack session creates its own fresh Signal group (amplihack-<session-id>-<unix-ts>) and leaves it at Stop, so no session can see another session's messages. Reusing a single shared "rolling" group is strictly opt-in.


AMPLIHACK_SIGNAL_REUSE_ROLLING_GROUP

Type: boolean (truthy: 1, true, yes, on) TOML key: reuse_rolling_group Default: false (per-session groups) Read by: amplihack_signal::config::SignalConfig

Opt-in switch for sharing one long-lived group across every session. When truthy, amplihack reuses the group named by AMPLIHACK_SIGNAL_ROLLING_GROUP_ID and does not quit it at Stop, giving one persistent operator thread. When unset, empty, or an explicit false value (0, false, no, off), the channel resolves fail-closed to the per-session default — never to shared visibility. Unknown non-empty tokens are rejected so typos are visible. A truthy value without a non-empty AMPLIHACK_SIGNAL_ROLLING_GROUP_ID is rejected so rolling mode cannot create an untracked group. Like every Signal setting, the environment variable overrides the TOML key of the same name.


AMPLIHACK_SIGNAL_ROLLING_GROUP_ID

Type: string (signal-cli group id) TOML key: rolling_group_id Default: unset Read by: amplihack_signal::config::SignalConfig

Binds rolling reuse to an existing group id. Only consulted when AMPLIHACK_SIGNAL_REUSE_ROLLING_GROUP is truthy; it is ignored while the per-session default is in effect. Setting this alone does not enable sharing — both the opt-in flag and this id are required for a shared rolling group.