Environment variables
Most of what Lattice reads from the environment also has a flag, and where both are given the flag wins. See option precedence. Lattice also sets a few variables in the environment of the commands it spawns.
What Lattice reads
| Variable | Flag | Effect | Counts as set when |
|---|---|---|---|
CI |
-v/--verbose |
Forces raw output instead of the interactive display. | Present, any value, including empty |
NO_COLOR |
none | Disables ANSI color. Nothing in lattice.json or on the command line turns color back on once this is set. |
Present, any value, including empty |
LATTICE_NO_VERSION_CHECK |
--no-version-check |
Suppresses both the version-drift nag and the automatic switch to a pinned latticeVersion. settings.versionCheck: false does the same for the whole repo. See Upgrading. |
Present, any value, including empty |
LATTICE_SWITCHED_FROM |
none | Set by Lattice on the process it hands an invocation to after a version switch, so that process does not switch again. Not meant to be set by hand. | Present, any value |
LATTICE_THEME |
--theme |
light or dark, case-insensitive. Forces the teal shade used in the splash and logo art. Any other value is ignored. |
A recognized value is present |
COLORFGBG |
--theme |
Splash theme only, read as fg;bg or fg;...;bg. A trailing 7 or 15 means a light background, anything else dark. |
Neither --theme nor LATTICE_THEME gives a recognized value, and this parses |
LATTICE_RELEASE_BASE_URL |
--release-base-url |
Base URL that lattice upgrade and the automatic version switch download release archives from. A file:// base needs no network. |
Present and not empty or whitespace-only |
LATTICE_RELEASE_LATEST_URL |
--release-latest-url |
Endpoint that resolves lattice upgrade latest to the newest stable release. |
Present and not empty or whitespace-only |
LATTICE_RELEASE_LIST_URL |
--release-list-url |
Endpoint used as a fallback when the latest-stable endpoint has nothing to name, because no stable release has been published. | Present and not empty or whitespace-only |
--theme and --release-base-url are global: they parse on lattice itself
and on every subcommand. --release-latest-url and --release-list-url live on
lattice upgrade, the only command that asks those endpoints anything.
Presence is the whole test for CI, NO_COLOR, LATTICE_NO_VERSION_CHECK, and
LATTICE_SWITCHED_FROM. CI= counts the same as CI=1 or CI=false. The
three LATTICE_RELEASE_*_URL overrides instead treat an empty or
whitespace-only value as unset and fall back to the default, so an inherited
LATTICE_RELEASE_BASE_URL= does not break the default download path. A blank
value passed to the matching flag is treated the same way.
CI and -v/--verbose are independent triggers of the same raw mode.
Neither overrides the other, and nothing forces interactive mode back on from
inside a CI=1 environment. See Output and
logging for the rest of how the output mode is
chosen.
Across a version switch
A repo that pins latticeVersion hands the invocation to the pinned build. Your
command line passes through unchanged, and LATTICE_SWITCHED_FROM is set on the
new process so it does not switch again. That build can be older than the flags
you typed, and an unrecognized flag is a parse error.
LATTICE_SWITCHED_FROM therefore has no flag. A build too old to know the flag
would reject it and fail the handover, while a variable it does not read is
ignored. For the same reason, exporting LATTICE_RELEASE_BASE_URL reaches a
pinned build that predates --release-base-url, where passing the flag would
not. See Upgrading for the switch itself and for how
the three version-check suppressions interact.
What Lattice sets for a spawned command
A task’s command runs through the platform shell, sh -c on Unix and cmd /C
on Windows, as a child process that inherits the full environment Lattice itself
was invoked with. Nothing is cleared. On top of that inheritance, Lattice sets:
| Variable | Value | When |
|---|---|---|
PATH |
The task’s resolved toolchain bin directories, then the project’s dependency bin directories, prepended in that order ahead of the inherited PATH. |
Toolchain directories on every task with a provisioned engine and on every lattice setup install command; dependency directories on every task command |
Each name listed in a task’s env |
The value read from Lattice’s own environment at the moment the cache key was computed. | Every task that declares env |
LATTICE_TOOLCHAIN_DIR |
Absolute path to the toolchain’s install directory, both as a variable and literal-substituted into the installCmd string. |
Only while an engine’s installCmd runs, never for the task command that follows |
PATH is scoped to that one child process, and never touches the shell Lattice
itself is running in. See Engines and provisioning for
what gets installed into LATTICE_TOOLCHAIN_DIR and how the resulting bin
directory reaches PATH.
Tools the project installed
A package manager installs a project’s executables somewhere you never type the
path to, and puts that directory on PATH itself whenever it runs one of your
scripts. Lattice hands a task’s command to the shell directly, so it adds those
directories too. Without them a task reading eslint src would fail with
eslint: command not found in a repo where eslint is an ordinary dev
dependency.
Before running a task, Lattice walks from the workspace directory up to the repo root and prepends every one of these that exists, nearest directory first:
| Directory | Installed by |
|---|---|
node_modules/.bin |
npm, pnpm, yarn, bun |
vendor/bin |
composer |
.venv/bin, .venv/Scripts |
uv, poetry, pdm, pipenv, python -m venv |
venv/bin, venv/Scripts |
the same, under the other conventional name |
Nearest first is the order a package manager itself resolves in, so a workspace that installs its own copy of a tool gets that copy rather than the root’s. The walk stops at the repo root and never reaches a directory above it.
These go on after the toolchain directories, never before. A version pinned in
engines exists to decide which copy of a tool runs, and a dependency directory
that shadowed the pin would undo that. Only directories that exist are added, so
a repo pays nothing for the ecosystems it does not use, and none of these paths
is hashed into a task’s cache key — what matters to the key is the toolchain
identity and the files the task reads.
The directories have to exist before they can be added, which is what
lattice setup is for. On a fresh clone, run it before the first
lattice run.
How a task’s env list resolves
env on a task is a list of variable names, not NAME=value pairs:
{
"tasks": {
"build": {
"inputs": ["src/**/*"],
"env": ["NODE_ENV", "API_BASE_URL"]
}
}
}
Lattice reads each name from its own process environment, meaning whatever was
exported into the shell you ran lattice from. It does not load .env files,
so a variable defined only in one is unset as far as Lattice is concerned. A
name with no value is hashed as declared-and-unset, which is a different key
from not listing the name at all.
The resolved name and value pairs are sorted by name and hashed into the task’s cache key alongside its command, its input files, and its resolved toolchain identity. See Caching for the rest of what feeds that key. Lattice sets those same values on the task’s own process, on top of the full inheritance described above.
Listing a variable ties the cache key to its value. Leaving it out does not hide
the variable from the task, because inheritance ignores env, but the key no
longer moves when the value changes, and the task can be served a hit computed
under a different value.
Repo-wide globalEnv
A variable that changes what every task produces can go in the root-level
globalEnv instead of being repeated in each task’s env:
{
"globalEnv": ["NODE_ENV", "CI"]
}
The names resolve the same way and are hashed into every task’s key. The one
difference is that globalEnv names are not set on the task’s process the way
env names are, because they are already in the environment Lattice inherited.
A task’s own env list still applies on top.