Errors

Every message Lattice fails on, plus the warnings it prints and the two messages that read like failures and are not. Entries are grouped by the stage of a run that raises them. Each message appears on one line, the way the program prints it, so a find in this page (Ctrl-F, or Cmd-F) matches what you just saw in your terminal.

A fatal error goes to stderr as Error: followed by the message, and the process exits 1. When one error wraps another, a Caused by: block follows with the inner message. A warning does not stop the run. Raw output prefixes a warning with lattice: warning: , and adds <workspace>:<task>: after that when the warning belongs to one task. The interactive display prefixes it with a yellow warn instead. Each entry below says which it is.

For symptom-first fixes, see Troubleshooting. For the fields these messages name, see Configuration. For the exit codes, see the CLI reference.

Config loading and validation

Raised while finding, reading, parsing, and validating lattice.json. Nothing has run at this point.

No lattice.json found

no lattice.json found in this directory or any parent. Run `lattice init` to create one

Raised by lattice run, lattice setup, lattice prune, lattice stats, and lattice upgrade when the walk up from the working directory reaches the filesystem root without finding a lattice.json. All five print the same text. Fatal. Run lattice init, or change into the repo.

Config file unreadable or unparseable

failed to read lattice.json from /repo
failed to parse lattice.json

The first wraps a filesystem error: a permission denial, or a directory named lattice.json. The second is the head of a parse failure, and the Caused by: block underneath carries the reason with its line and column. Both fatal.

Unknown key in lattice.json

unknown field `output` in tasks.build (lattice.json line 3, column 32)
Did you mean `outputs`?
Fields accepted here: dependsOn, inputs, outputs, ignore, env, persistent, cache, timeout

Raised while parsing, for a key the config does not accept at that level. The first line names the key, the object holding it, and where it sits in the file. The container reads the way it does in the file: workspaces[1], tasks.build, engines.node, or at the top level of lattice.json. Did you mean appears when an accepted field is within one or two edits. Fields accepted here lists the accepted keys for that container, so the list differs by container. Fatal, and raised before any workspace is read. Delete the key, or correct it to the field the message names. See Configuration.

The same key written twice

duplicate key `build` in tasks (lattice.json line 1, column 58)
Keep one of them: the second replaces the first, so only the last would take effect

Raised while parsing, for a key that appears more than once in one of the four name-keyed maps: tasks, engines, a workspace’s engines, or a workspace’s scripts. Fatal. The container reads the way it does in the file: tasks, engines, workspaces[0].engines, or workspaces[0].scripts.

The position is where the JSON parser finished the object, so it lands on the closing brace rather than on the repeated key. Search the container the message names for that key.

Only the last value for a repeated key would survive, so a second "build" would discard the first one’s outputs, inputs, and env. Delete whichever of the two you do not want.

Everywhere else in the file, a repeated key is a repeated field on a fixed object, and serde reports it under Caused by:. A top-level key, a key inside settings, a key on a workspace entry, and a key inside one task all read like this:

duplicate field `path` at line 1 column 52

Engine value in neither accepted form

invalid type: integer `20`, expected a version constraint string or an engine object

An engines entry whose value is neither a version-constraint string such as ">=20.0.0" nor an object such as { "version": ">=20.0.0" }. Fatal, under a Caused by: beneath failed to parse lattice.json.

Engine declared in the string form Lattice cannot check

engine 'alpes' in root uses the string form, which carries only a version. 'alpes' is not a well-known engine, so Lattice cannot version-check it on its own. Use the object form with a `versionCmd`, like this: "alpes": { "version": ">=1.0.0", "versionCmd": "alpes --version" }

Raised during validation for an engines entry written as a bare version string whose name is not on the well-known engine list. in root becomes in workspace '<name>' for a per-workspace engine. Fatal. Switch to the object form the message shows. The well-known list is on Toolchains.

Workspace has an empty path

workspace 'web' has an empty path

The path field is present but blank or whitespace-only. Fatal.

Workspace path is not relative to the repo root

workspace 'abs' has a path '/tmp/x' that is not relative to the repo root. Write every workspace path relative to the repo root

The path is absolute. Fatal. Write it relative to the directory holding lattice.json.

Workspace path points outside the repo

workspace 'esc' has a path '../outside' that points outside the repo root. Every workspace path must stay inside the repo

The path climbs above the repo root with ... Fatal. The workspace directory bounds which files are hashed, which files the outputs globs match, and which files a cache hit clears before it unpacks, so it has to sit inside the repo.

Workspace path has whitespace around a directory name

workspace 'web' has a path 'apps/web ' with leading or trailing whitespace around a directory name. Windows drops that whitespace and unix keeps it, so the path would name a different directory on each. Remove it

A component of the path begins or ends with a space or a tab. Fatal. Lattice rejects apps/web, apps/web , and apps/ web alike. A lattice.json is committed and shared, so the check runs on every platform rather than on the one whose path rules the value happens to break.

A path written with Windows separators, such as apps\web, passes this check. On unix that string is one filename rather than two path components, so the check finds no component with whitespace around it. The path fails later, when Lattice resolves the directory, with workspace path ... does not point to a directory.

Workspace dependsOn names an undeclared workspace

workspace 'api' depends on 'cor', which is not a declared workspace
Did you mean `core`?
Declared workspaces: core, api

A name in a workspace’s dependsOn matches no entry in workspaces. Fatal. An unresolvable name builds no edge, so ^task would expand to nothing and the ordering the config was written to guarantee would not happen. Did you mean appears when a declared name is close enough to be a typo. A workspace that lists itself gets its own message:

workspace 'api' lists itself in `dependsOn`

Also fatal.

Task dependsOn names an undefined task

task 'build' depends on 'codegen', but 'codegen' is not defined in `tasks`
Did you mean `codegin`?
Defined tasks: build, test, codegin

A name in a task’s dependsOn has no entry in the tasks map. Fatal, for the same reason: the prerequisite would resolve to no node and not run. The ^ prefix is stripped before the check, so ^build is checked against build. A task that lists itself gets its own message:

task 'build' lists itself in `dependsOn`

Also fatal.

Duplicate workspace name

duplicate workspace name 'web'. Every workspace name must be unique

Two entries in the workspaces array share a name. Fatal. A second check for the same condition exists at the discovery stage with different text, but this one runs first, so this is the message you see. See Duplicate workspace name or path at discovery.

settings.cacheDir does not name a usable directory

`settings.cacheDir` is empty. Name a directory, like ".lattice/cache", or leave the key out to use that default
`settings.cacheDir` is '.', which is the repo root itself. Point it at a directory of its own, like ".lattice/cache" — `lattice prune` deletes cache archives and partial writes in whatever directory it names
`settings.cacheDir` is '/tmp/lattice', which is not relative to the repo root. Write it relative to the repo root, like ".lattice/cache"
`settings.cacheDir` is '../cache', which points outside the repo root. The cache directory must stay inside the repo
`settings.cacheDir` is 'build /cache', which has leading or trailing whitespace around a directory name. Windows drops that whitespace and unix keeps it, so the path would name a different directory on each. Remove it

All five fatal, raised during validation. Lattice owns the directory cacheDir names: lattice prune deletes the *.tar.gz, *.meta.json, and *.tmp files it finds there. The value therefore has to be relative, has to stay inside the repo, and cannot be ., ./, or anything else that resolves to the repo root. .lattice/cache is the default. .cache/lattice also passes.

Engine bin does not stay inside the toolchain

engine 'node' in root has an empty `bin`. Leave `bin` out to use the default of "bin", or name a directory inside the toolchain install
engine 'node' in root has a `bin` of '/usr/bin', which is not relative to the toolchain install. Write `bin` as a path inside the toolchain directory, like "bin"
engine 'node' in root has a `bin` of '../../..', which points outside the toolchain install. Write `bin` as a path inside the toolchain directory, like "bin"
engine 'node' in root has a `bin` of 'bin ', which has leading or trailing whitespace around a directory name. Windows drops that whitespace and unix keeps it, so the path would name a different directory on each. Remove it

All four fatal, raised during validation. in root becomes in workspace '<name>' for a per-workspace engine. Lattice joins bin to the toolchain’s install directory and puts the result at the front of the PATH of every task that resolves that engine. An absolute value replaces the install path outright. The task then runs against a directory Lattice never provisioned, while the run still reports a provisioned toolchain. bin defaults to "bin". ., usr/local/bin, and bin/../libexec are all accepted.

A scripts key names no declared task

workspace 'core' declares a script 'biuld', but 'biuld' is not defined in `tasks`, so nothing would ever run it
Did you mean `build`?
Defined tasks: build, test

A key in a workspace’s scripts map that has no matching key under the root tasks map. Fatal, raised during validation.

A scripts entry supplies the command for the root task of the same name and does nothing else, so no configuration can reach a key outside tasks. An earlier version accepted and stored a typo like biuld. The workspace then ran the command Lattice detected for it, and no message mentioned the override. Correct the key to the task name, or add the task to tasks.

Did you mean appears when a defined task name is within one or two edits of the key. With no tasks map at all there is nothing to suggest, so the last line names what to add instead:

Add 'build' to `tasks` in lattice.json, or remove the script

Invalid cache size

Raised while parsing a cache size, which happens for settings.maxCacheSize in lattice.json and for lattice prune --max-size:

cache size is empty
cache size 'XB' does not start with a number
could not read the number in cache size '10..5GB'
unknown cache size unit 'XB' in '10XB'. Use B, KB, MB, GB, or TB

All fatal. A valid size is a number followed by B, KB, MB, GB, or TB, case-insensitive and base 1024, or a bare integer of bytes: "10GB" or "1048576". The third message carries the number-parsing detail under Caused by:, such as invalid float literal. From settings.maxCacheSize all four arrive under failed to parse lattice.json. From --max-size they arrive on their own.

Invalid duration

Raised while parsing a task’s timeout:

duration is empty
duration 'XB' does not start with a number
could not read the number in duration '10..5s'
unknown duration unit 'y' in '10y'. Use ms, s, m, or h
duration '0s' must be greater than zero
duration '99999h' is longer than the maximum of 365 days. Use a shorter duration, or leave `timeout` out to let the task run without a limit
duration of 18446744073709551615 seconds is longer than the maximum of 365 days. Use a shorter duration, or leave `timeout` out to let the task run without a limit
duration 1.5 is not a whole number of seconds. Write a whole number of seconds, or a duration string such as "90s", "1500ms", or "10m"

All fatal, and all arrive under failed to parse lattice.json. A valid duration is a number followed by ms, s, m, or h, or a bare whole number of seconds: "10m", "90s", "90", 90. A duration that rounds below one second is rejected rather than rounded to zero, which would kill the task the moment it started.

The maximum is 365 days, 31536000 seconds. The two over-maximum messages differ only in the form the value was written in: the first covers a string, the second covers a JSON number. Past 365 days the seconds no longer fit the arithmetic the runner does on them. A deadline that overflows is never reached, so an oversized timeout used to mean no timeout at all. A timeout left out lets the task run without a limit.

The fractional message covers the number form only. Lattice rejects 1.5 rather than rounding it up to 2, because the bundled JSON schema has always declared this field an integer. An editor validating against that schema would have flagged the same value. The string form takes a fraction of a unit, so "1.5m" and "90s" both parse. 90.0 is a whole number of seconds and parses.

prune has no limit to work to

no cache size limit set. Pass --max-size, or set settings.maxCacheSize in lattice.json

lattice prune ran with neither --max-size nor settings.maxCacheSize. Fatal. Pass the flag, or set the field.

Workspace discovery and driver detection

Raised while resolving the workspaces array: checking that each path is a directory, and picking each workspace’s driver from the evidence in it. See Driver detection.

Ambiguous or undeclared driver

The evidence ladder’s halt. Fatal. The text is built from what Lattice found in the workspace directory, so it takes three shapes.

No evidence at all, such as a directory holding only a .nvmrc:

workspace 'app' has an ambiguous or undeclared driver.
Lattice detected no driver. The directory holds no lockfile, no wrapper, and no native declaration.
Declare the driver in lattice.json, under this workspace:
  "auto": false, "scripts": { "build": "<command>" }

A bare ecosystem marker with no tool-unique signal, such as a package.json with no lockfile:

workspace 'app' has an ambiguous or undeclared driver.
Candidate drivers: pnpm, npm, yarn, bun
Declare the driver in lattice.json, under this workspace:
  "engines": { "pnpm": ">=0.0.0" }

Two drivers of the same role at once, such as both bun.lockb and pnpm-lock.yaml:

workspace 'web' has an ambiguous or undeclared driver.
Candidate drivers: bun, pnpm
Declare the driver in lattice.json, under this workspace:
  "engines": { "bun": ">=0.0.0" }

Paste the suggested line into that workspace’s entry in lattice.json, naming whichever driver you use if it is not the one suggested. The suggestion always resolves the halt: an engines entry when a candidate can drive tasks, and the scripts form when none can, because a runtime alone never drives a named task. This halt fires only for a workspace whose auto is true, which is the default.

Workspace path is not a directory

workspace path 'apps/gone' does not point to a directory. A workspace path is one literal directory, not a glob

A configured path does not resolve to an existing directory. Fatal. See Workspaces.

Workspace path resolves outside the repo

workspace path 'app' resolves to /elsewhere/app, which is outside the repo root. A workspace directory has to be inside the repo

The path is relative to the repo root and passes validation, and the directory it names is a symlink that points out of the repo. Fatal. A workspace directory bounds what its tasks read and write, and that bound follows the directory the path resolves to rather than the text of the path. A symlink that stays inside the repo is still a workspace.

Duplicate workspace name or path at discovery

duplicate workspace name 'web' in lattice.json
duplicate workspace path 'apps/web' in lattice.json

Raised during discovery. Both fatal. Validation rejects a duplicate name earlier with different text, so in practice the path form is the one you see. It fires even when two entries carry different names, once their paths canonicalize to the same directory.

Task graph construction

Raised while expanding the root tasks map across the resolved workspaces into the execution graph. See Task graph.

Unknown task

lattice run checks every task name you passed before it builds any graph:

task 'lint' is not defined in the `tasks` map in lattice.json. Defined tasks: build, test

The tail becomes lattice.json defines no tasks when the tasks map is empty. Fatal. Graph construction carries the same check for callers that skip the pre-check, without the tail:

task 'lint' is not defined in the `tasks` map in lattice.json

Through the CLI you always see the first form.

A manual workspace declares no command for a requested task

workspace 'legacy' has "auto": false and declares no command for task 'build'. Add the command under this workspace's "scripts" map in lattice.json

A workspace with auto: false infers nothing and never invents a command, so a requested task with no entry in that workspace’s scripts map halts the run. Fatal. An auto: true workspace instead skips a task its driver does not provide. A --filtered run holds only the matched workspaces to this check: one pulled in as a dependency is asked for the tasks its dependents need, not for the task you named.

A task was skipped because the manifest declares no such script

An auto workspace whose driver reads its tasks out of a manifest can run only a script that manifest declares. A requested task the manifest does not declare drops out of the graph and does not run there. A package with nothing to build and a typo look the same from outside, so Lattice warns whenever a manifest declares a script map without the task you asked for:

web declares scripts but no "build", so the task was skipped. Did you mean "biuld"?

The tail names the closest declared script when one is close enough to be a plausible typo. When no declared script is that close:

web declares scripts but no "build", so the task was skipped. Declare it in the workspace's manifest, or under "scripts" in lattice.json, if the task should run there

Two or more skips collapse into one line, each carrying its own suggestion:

some tasks were skipped: docs declares scripts but no "build"; web declares scripts but no "build" (did you mean "biuld"?). Declare each in the workspace's manifest, or under "scripts" in lattice.json, if the task should run there

The noun after declares is the manifest’s own word for its script map: scripts for npm, pnpm, yarn, and bun, tasks for deno. Warnings, emitted once per run, on a real run and on --dry-run alike.

Two things narrow the warning and one does not. It covers only the tasks you asked for, so lattice run build says nothing about a missing lint. It leaves out a workspace whose auto is false, because such a workspace infers no tasks at all. --filter does not narrow it. lattice run build --filter api can still name web, because Lattice reports a manifest that should declare build and does not, whether or not the run selected its workspace.

A manifest with no script map at all draws no warning. A types-only package with no build script is a complete configuration, not a mistake.

A manifest could not be read

One warning per workspace, whatever the reason:

web: package.json could not be parsed: expected `,` or `}` at line 4 column 3, so every task it would have named was skipped
web: package.json could not be read: Permission denied (os error 13), so every task it would have named was skipped
web: package.json has a "scripts" that is not an object, so every task it would have named was skipped

The file name is the one that workspace’s driver reads: package.json, or deno.jsonc when the directory holds one and deno.json otherwise. Warnings, not errors. The workspace runs nothing, and Lattice guesses no command for it.

Lattice strips // and /* */ comments before parsing a deno.json or a deno.jsonc, so a commented Deno config resolves its tasks. It parses a package.json as strict JSON, the way npm does, so a comment in a package.json is a parse failure and reads as package.json could not be parsed.

A persistent task is depended on

task 'dev' in workspace 'web' is persistent, so no other task may depend on it

A persistent: true task is not expected to exit, so nothing that waits for it could ever start. Fatal. See Persistent tasks.

The task graph has a cycle

the task graph has a cycle

The dependsOn edges, same-workspace or ^-prefixed, form a cycle, so no topological order exists. Fatal. The message does not name the tasks in the cycle.

Toolchain resolution and provisioning

Raised while satisfying each engines constraint. The shape of the constraint picks host mode, validate-only, or provisioning. Host mode installs nothing and checks nothing, so it raises none of these. The other two each fail their own way. See Engines and provisioning.

Validate-only failures

For an engine with a version constraint and no installCmd.

engine 'alpes': version command `alpes --version` failed:
sh: alpes: command not found

The version command exited non-zero. The combined stdout and stderr follow on the next lines. Most often the tool is not installed.

engine 'alpes': could not read a version from the output of `echo unknown`:
unknown

The version command succeeded, but its output held no recognizable number. Lattice takes the first run of digits and dots it finds. The output follows on the next lines.

engine 'alpes' has a version constraint but no way to check the installed version. 'alpes' is not a well-known engine, so add a `versionCmd` to it

An object-form engine carries a version but no versionCmd, and its name has no built-in version command. Add a versionCmd.

engine 'alpes' on PATH is 1.4.0, which does not satisfy the constraint '>=2.0.0'

The tool is present and its version parsed, but the version does not satisfy the constraint. Install a satisfying version on PATH, loosen the constraint, or add an installCmd so Lattice provisions the version itself.

All four fatal, and all four raised before any task starts.

Provisioning failures

For an engine with an installCmd.

failed to create toolchain dir /repo/.lattice/toolchains/alpes

Wraps a filesystem error creating the content-addressed install directory.

engine 'alpes': installCmd failed:
curl: (6) Could not resolve host: alpes.example

The installCmd exited non-zero. Its combined stdout and stderr follow.

failed to move toolchain into place: /repo/.lattice/toolchains/alpes/tmp-a1b2c3d4 -> /repo/.lattice/toolchains/alpes/1.4.0-a1b2c3d4

The install succeeded, but renaming the staged directory to its final, version-stamped path failed. Usually a filesystem or permission problem.

engine 'alpes': version command `alpes --version` failed after install:
sh: alpes: command not found

The install command exited 0, but the version command run against the freshly installed bin directory failed. Most often the engine’s bin path is wrong, so the installed tool is not on the PATH the check uses.

engine 'alpes' provisioned 1.4.0, which does not satisfy the constraint '>=2.0.0'

The freshly installed tool reports a version the declared constraint rejects. The installCmd installed the wrong version.

engine 'alpes' has the version constraint '>=2' but no way to check what was installed. 'alpes' is not a well-known engine, so add a `versionCmd` to it

The install succeeded and the engine carries a version. There is no versionCmd and no built-in version command for that name, so nothing can check the constraint against the tool that was installed. Add a versionCmd, or drop the version.

engine 'alpes': could not read a version from the output of `alpes --version` after install, so the constraint '>=2.0.0' cannot be checked:
built from source

The version command ran and exited 0, and its output held no recognizable number, so the constraint has nothing to test. The output follows on the next lines.

Both of these used to record 0.0.0 and carry on. Nothing had installed that version, nothing had checked the constraint, and the fabricated number went into pins.json and into every cache key. With no version constraint there is nothing to fail on, so Lattice records the version as unknown and identifies the toolchain by its install hash.

All seven fatal. Two more failures underlie both modes:

the pinned toolchain cannot be put on PATH, because a directory in it contains a character PATH cannot hold: /repo:2/.lattice/toolchains/alpes/1.4.0-a1b2c3d4/bin

A toolchain directory holding : on unix cannot go on a PATH, because the separator would split that one directory into two paths that do not exist. On Windows the offending character is " rather than the ; separator, which Windows quotes instead of refusing. The fallback would be the inherited PATH, which runs whatever version of the tool the machine happens to have. Pinning exists to prevent that outcome, so every place that builds a pinned PATH fails instead:

  • Running an engine’s version or install command. Fatal, and the message names the one directory involved.
  • lattice setup’s dependency installer. Fatal, and the message lists every prepended directory separated by , .
  • Spawning a task. Lattice reports the failure as that task’s own, with this message as its output, so the run stops or carries on under --continue like any other failure. An earlier version dropped the pin without a word and ran the task against the host’s tool while still reporting a provisioned toolchain.

Rename the directory, or move the repo somewhere without that character in its path.

failed to spawn `alpes --version`

Wraps an OS error spawning the shell that runs a version or install command. In practice, sh itself is missing.

Cache operations

Raised by cache-key computation and cache storage. See Caching for the model and Cache internals for the on-disk format.

A lookup is a hit only when the metadata parses, the artifact’s recorded size matches the file on disk, and the artifact’s sha256 matches the recorded digest. Everything else is a miss, and the task runs. A lookup never raises an error and never warns: unparseable metadata, a missing artifact, a truncated artifact, and a digest mismatch are all misses.

A cache key could not be computed

failed to compute cache key: failed to read input file /repo/apps/app/src/main.rs

Raised before a task starts, when reading a file the key covers fails. Usually a file that disappeared between the glob and the read, or one the process cannot read. Fatal for that task, and the run then behaves as it does for any task failure. This is the only cache error that fails a task.

The wrapped part names the file. Every file the key covers uses the same wording, whether it is an inputs match, a manifest, a lockfile, or a globalDependencies match:

failed to read input file <path>
failed to stat <path>
failed to read <path>
<path> changed while Lattice was hashing it. Expected 4096 bytes, read 2048

The last one fires when a file is rewritten mid-hash, which would otherwise record a key for bytes that never existed together.

Restore and store warnings

Warnings, carrying the workspace and task they belong to, so raw output prints each of these after lattice: warning: app:build: . A restore failure falls through to running the task. A store failure is reported after a successful run.

failed to restore cached outputs: failed to unpack artifact into /repo/apps/app
failed to cache outputs: no files matched outputs ["dist/**"], so nothing was cached. Check that the patterns are relative to the workspace, and that the task writes there
failed to cache outputs: outputs ["dist"] matched only empty directories, so nothing was cached. Check that the task writes its files where the patterns point
failed to cache outputs: failed to create cache dir /repo/.lattice/cache

The second and third are the ones you are most likely to hit. In both, the task succeeded, declared outputs, and produced no file the patterns cover, so Lattice stores nothing and the next run misses again.

The two messages differ in what the patterns matched. no files matched outputs means they matched nothing at all. matched only empty directories means a pattern named a directory that exists and holds no files. A bare outputs: ["dist"] against an empty dist/ reads that way. Storing an archive of an empty directory would make every later run a hit that restores an empty dist/ over whatever the last real run wrote, so Lattice refuses that case too.

Each wraps one of the cache-storage errors, which never appear on their own: failed to create cache dir <path>, failed to create artifact <path>, failed to add <path> to artifact, failed to digest artifact <path>, failed to write artifact <path>, failed to write cache metadata at <path>, failed to open artifact <path>, and failed to unpack artifact into <path>. None of them fails the run.

prune cannot read the cache directory

failed to read cache dir /repo/.lattice/cache

lattice prune treats a missing cache directory as nothing to prune. Any other read error, such as a permission denial, is fatal.

Task execution

Raised by the scheduler while running task commands.

A task failed and the run stopped

task 'app:build' failed, stopping the run

The first task failure in a run without --continue. No further tasks start, though any already in flight run to completion. This is the process’s fatal error and the exit code is 1. The task’s own captured output is printed above this line.

A task failed and the run kept going

With --continue, independent branches keep running and anything downstream of a failure is skipped rather than started. No error line prints in this case. The run summary carries the counts, and the process exits 1:

lattice: 2 tasks, 0 cached, 2 failed, 0.01s

Search the per-task lines above the summary for FAILED to find which tasks failed. Each of those lines carries the exit code the command returned and how long it ran, and the task’s captured output prints under it. --sequentially applies the same rule per phase: a failing phase stops the remaining phases unless --continue is also set, in which case every phase runs and the process still exits 1.

A task’s shell could not be spawned

failed to spawn task shell (is `sh` available?): No such file or directory (os error 2)

The platform shell, sh on unix and cmd on Windows, could not be spawned at all. This is not the task’s own command failing, which is an ordinary non-zero exit. Any spawn error other than “not found” uses a shorter form:

failed to spawn task: <os error>

Both are reported as that task’s failure. A task can also fail before its shell runs at all, when the workspace’s pinned toolchain cannot go on the task’s PATH. See Provisioning failures.

A task overran its timeout

timed out after 10m and was stopped

Reported with the task’s captured output, the way any other task failure is. The task’s whole process group is sent SIGTERM, given five seconds, then killed. The duration is rendered in the largest exact unit, so "timeout": "600s" reports 10m. The task counts as a failure, so the run then stops or carries on under --continue like any other failure. A persistent task has no effective timeout whatever timeout says.

A task whose command ignores SIGTERM, or that backgrounded something still holding its output pipes, reports up to five seconds after the timeout itself. Those five seconds are the grace period, and Lattice kills the process group outright once it runs out. Such a task previously never reported at all. The run kept waiting on pipes that nothing was going to close.

The run was interrupted

Ctrl-C, or a SIGTERM delivered to Lattice, stops the run. Every running task’s process group is sent SIGTERM, given five seconds, then killed. No error line prints. The run summary prints and the process exits 130 rather than 1:

lattice: 1 tasks, 0 cached, 0 failed, 0.00s

The tasks that were running are not counted as failures, because the run was stopped rather than broken. 130 is the shell’s convention for SIGINT, 128 + 2, which lets a CI runner tell a cancelled job from a failed build.

The runner panicked

task runner panicked: <panic message>

A spawned task’s internal work panicked. Fatal regardless of --continue, since the scheduler’s own state can no longer be trusted. This is a bug in Lattice rather than a task failure. Report it.

lattice setup failures

workspace 'wbe' is not declared in the `workspaces` array in lattice.json. Declared workspaces: api, web

A name passed to lattice setup that no workspace entry declares. Fatal, and raised before Lattice provisions or installs anything. The tail lists the declared names alphabetically, and becomes lattice.json declares no workspaces when the array is empty. lattice setup used to select nothing for an undeclared name and exit 0, so a typo in a CI script looked like a clean run.

web: `pnpm install` failed

A workspace’s dependency-install command exited non-zero. A warning per workspace, so raw output prefixes it with lattice: warning: . Setup carries on to the remaining workspaces. Once every selected workspace has been attempted, one or more failures fail the command:

setup failed in one or more workspaces

Fatal. This is what makes lattice setup exit non-zero.

web: dependencies installed, but .lattice/setup/apps%2Fweb.marker could not be written, so the next `lattice setup` will install again: Permission denied (os error 13)

The install succeeded and writing the marker that records it failed, so nothing remembers the install and the next lattice setup repeats it. A warning rather than an error, and the command still succeeds. The path is the workspace’s own marker under .lattice/setup, and the error from the operating system follows the colon. See lattice setup for how the marker is named.

An installer that wants to prompt is a third case. lattice setup gives the install command no stdin, so an installer that asks for a password or a confirmation reads end-of-file and exits non-zero. Its own message about what it wanted appears under web: \pnpm install` failed`. The install command used to inherit the terminal and wait on a prompt nothing was showing, until something killed the run. Supply the credential without a prompt, through an environment variable or a configured keychain helper. Running the installer once yourself, outside Lattice, also works.

Two conditions that are not errors

Both print a message and exit 0. A --filter that matches no workspace:

lattice: no workspaces matched filter '<pattern>'.

And a repo whose workspaces array is empty:

lattice: no workspaces declared. Add one to the `workspaces` array in lattice.json, then run `<tasks>`.

<tasks> holds the task names you passed, joined by spaces.

Version pinning and self-update

Raised by lattice upgrade and by the automatic handover to a pinned version that runs before most commands. See Upgrading for the normal path.

Not a version

'v1.x' is not a version. Write it like 0.2.0

The argument to lattice upgrade did not parse as semver. Fatal. The Caused by: line carries the parser’s own complaint, such as unexpected character 'x' while parsing minor version number.

lattice.json pins `latticeVersion` as "1.x", which is not a version. Write it like 0.2.0, or run `lattice upgrade <version>` to set it

The same check against a repo’s latticeVersion, run before the handover to a pinned build. Fatal, and it names the field and the file rather than failing on a download of a release that cannot exist. The Caused by: block carries the message shown above this one, '1.x' is not a version. Write it like 0.2.0, with the parser’s complaint under that. Lattice accepts a v prefix and strips it, so "v1.1.1" and "1.1.1" pin the same release. When that release is the running build there is no handover to make, and the pin does nothing. A repo whose own tags carry the v prefix used to fail here on a download.

No downloader available

neither `curl` nor `wget` is on PATH. Install one of them, or download the release archive into .lattice/bin by hand

Fetching a release needs one of those two tools, and neither was found. Fatal.

A downloader was found but would not start

failed to run curl
failed to run wget

Lattice probed for curl and wget, found one, and could not launch it. This is not the tool running and reporting an error, which is Download or fetch failure. In practice it is a process limit, or a tool that left PATH mid-run. Fatal, with the OS error under Caused by:.

Download or fetch failure

failed to download https://github.com/latticeandcompany/lattice/releases/download/v0.4.0/lattice-0.4.0-aarch64-apple-darwin.tar.gz: curl: (6) Could not resolve host
failed to fetch https://api.github.com/repos/latticeandcompany/lattice/releases?per_page=20: curl: (6) Could not resolve host

download writes an archive to a file. fetch reads a response into memory. Both append the downloader’s own stderr. Both fatal.

Checksum mismatch

checksum mismatch for lattice-0.4.0-aarch64-apple-darwin.tar.gz
  expected 0000000000000000000000000000000000000000000000000000000000000000
  actual   edff58f2a441868dc58c35d06f2b1c86e12e12bedfaa793a49c227672f77566e
Lattice installs only a binary whose checksum matches the published release

The downloaded archive’s sha256 does not match the release’s checksums file. Fatal, and nothing is installed. A related failure fires when the checksums file does not cover this platform at all:

lattice-0.4.0-checksums.txt does not list lattice-0.4.0-aarch64-apple-darwin.tar.gz. This platform may have no published build for 0.4.0

Also fatal.

GitHub could not be reached to resolve latest

failed to ask GitHub for the newest release

lattice upgrade latest asks GitHub which release is newest and got no answer: offline, a DNS failure, a proxy, or a rate limit. Only latest needs the lookup, and a version number is used as given. Fatal, with the failed to fetch <url> detail under Caused by:. Retry, or name a version.

No release to install

no release to install. Tried https://api.github.com/repos/latticeandcompany/lattice/releases/latest and https://api.github.com/repos/latticeandcompany/lattice/releases?per_page=20

lattice upgrade latest reached both endpoints and neither named a release. Fatal.

The release archive could not be read or unpacked

failed to open /repo/.lattice/bin/.staging-0.4.0-12345/lattice-0.4.0-aarch64-apple-darwin.tar.gz
/repo/.lattice/bin/.staging-0.4.0-12345/lattice-0.4.0-aarch64-apple-darwin.tar.gz is not a readable tar.gz
failed to unpack lattice from /repo/.lattice/bin/.staging-0.4.0-12345/lattice-0.4.0-aarch64-apple-darwin.tar.gz

Three failures from the same step, after the download and its checksum both succeeded. The first is a filesystem error opening the archive, and it also fires one step earlier when hashing the archive for the checksum comparison cannot open it. The second means the bytes are not a readable gzipped tar, which a matching checksum does not rule out: a mirror set through --release-base-url can serve a corrupt archive alongside a checksums file that agrees with it. Damage further into the archive surfaces the tar library’s own message under Caused by: instead, such as unexpected end of file. The third means the lattice entry was found but writing it into the staging directory failed, typically a full disk or a read-only .lattice/bin. All three fatal.

The archive holds no binary

/repo/.lattice/bin/.staging-0.4.0-12345/lattice-0.4.0-aarch64-apple-darwin.tar.gz contains no lattice

The archive opened and read cleanly but held no lattice entry. Fatal. On Windows the name in the message is lattice.exe.

A version is not installed when linking

/repo/.lattice/bin/lattice-0.4.0 is not installed

Lattice was asked to point .lattice/bin/lattice at a version-stamped binary that is not on disk. Fatal. The install step always runs first in normal use, so this indicates an ordering bug rather than anything you configured.

An install directory could not be created or written

failed to create /repo/.lattice/bin
failed to move the binary into place: /repo/.lattice/bin/.staging-0.4.0-12345/lattice -> /repo/.lattice/bin/lattice-0.4.0

The first covers both .lattice/bin and the per-process staging directory underneath it. The second fires after a clean unpack, when moving the extracted binary to its version-stamped name failed. Both fatal, and both wrap a filesystem error.

Linking the stable path failed

failed to create symlink /repo/.lattice/bin/.lattice.link-tmp-12345
failed to move symlink into place at /repo/.lattice/bin/lattice

The binary installed, but pointing .lattice/bin/lattice at it failed. Both fatal, and both wrap a filesystem error. On Windows the stable path is a copy rather than a symlink, and the message is failed to copy <from> to <to>.

The automatic handover failed

this repo pins lattice 0.4.0. That version is not installed, and Lattice could not download it.
Run with --no-version-check to use lattice 1.1.1 instead

A binary Lattice installed under .lattice/bin hands the invocation to the version the repo pins, installing it first if needed. This wraps whatever install or download error stopped that, so the Caused by: block holds one of the checksum, download, or archive errors above. Fatal unless you pass --no-version-check, set LATTICE_NO_VERSION_CHECK, or set settings.versionCheck to false, any of which skips the handover so no error occurs. A binary Lattice did not install prints an advisory nag instead and changes nothing. See Environment variables.

The pinned binary could not be run

failed to run /repo/.lattice/bin/lattice-0.4.0

The pinned version installed and linked, and then handing the process over to it failed. Fatal, with the OS error under Caused by:.

lattice.json could not be read or written during upgrade

failed to read /repo/lattice.json
failed to write /repo/lattice.json

lattice upgrade reads the file to find the current pin and edits it as text in place, so key order and formatting survive the bump. Either step can fail on a filesystem error. Both fatal.