Output and logging
lattice run prints one of two ways. Interactive is a live display that
repaints in place while the run goes. Raw is one plain line per event, in
the order they arrive, with no cursor movement anywhere. Lattice picks one
before anything prints and keeps it for the whole run.
Every transcript on this page is a run of the same repo: four workspaces named
ui, web, api, and docs, where web and api depend on ui, and a
build task that declares inputs and outputs.
Check which mode you will get
Interactive is what a terminal gets. Any one of these gives you raw instead, and none of them can be reversed once it applies:
- stdout is not a terminal, so a pipe, a redirect, or a CI step.
CIis set to any value, including the empty string.- You passed
-v, or its long form--verbose. lattice.jsonsetssettings.loquacious.- The run pulls a
persistent: truetask into its graph. A dev server streams for as long as it is up, and the live display repaints in place, so it cannot show one. See Run dev servers.
To make every run in a repo print raw lines, put it in the config rather than in everyone’s shell history:
{
"settings": {
"loquacious": true
}
}
There is no flag that forces the live display back on. -v on the command line
beats a false setting, because the two are combined with OR.
What each mode prints for the same event
The runner reports events; the two modes are two renderings of the same list. Read across a row to translate a line you have in front of you.
| Event | Raw | Interactive |
|---|---|---|
| The run starts | lattice: running `build` across 4 workspaces (-v only) |
❖ lattice build · 4 workspaces |
| A task starts | ui:build: running |
⠋ ui:build running… |
| It finishes | ui:build: done (1.02s) |
✓ ui:build 1.02s |
| It comes back from cache | ui:build: cache hit [5341be25] |
● ui:build cache hit [5341be25] |
| It fails | ui:build: FAILED (code 3) after 1.02s |
✗ ui:build FAILED (code 3) 1.02s |
| A prerequisite failed, so it never starts | web:build: skipped (dependency failed) (-v only) |
○ web:build skipped (dependency failed) |
| A persistent task exits cleanly | docs:dev: exited (code 0) after 2.02s |
raw only |
| A persistent task exits non-zero | docs:dev: EXITED (code 1) after 2.02s |
raw only |
| A trace line | lattice: ui:build: hash 5341be25… (-v only) |
not shown |
| A warning | lattice: warning: ui:build: … |
warn ui:build: … |
| The run ends | lattice: 4 tasks, 0 cached, 0 failed, 3.08s |
❖ 4 tasks · 0 cached · 0 failed 3.08s |
| The run ends and something hit | lattice: 4 tasks, 1 cached, 0 failed, 3.09s, 3.02s saved |
❖ 4 tasks · 1 cached · 0 failed 3.09s · 3.02s saved |
| Every task came back from cache | lattice: full power, nothing to run |
❖❖❖ FULL POWER |
The two persistent rows have no interactive form. A run that can reach a
persistent task is switched to raw before it starts, so the live display never
gets to render one exiting. The trace row has no interactive form either. The
hash and cache-miss lines belong to -v, and the live display leaves them out.
How much a failure line carries depends on how the task died. A command that ran
and returned an exit code gets both halves, the code and the time. A task a
signal killed, and a task Lattice stopped for overrunning its timeout, have no
exit code, so the line carries the time alone: ui:build: FAILED after 30.00s.
A task that failed before its command ever started has neither, and the line is
the bare word FAILED. Its cache key would not compute, or its shell would not
spawn. It never ran, so there is no time to report.
Four details the table cannot hold. The run header lists every distinct task
name in the graph, sorted and joined with +, so lattice run lint build heads
its run build+lint in both modes, and so does a lattice run lint whose
lint depends on build. The workspace count is a real plural, so a run in one
workspace says across 1 workspace. Interactive pads the workspace:task label
to 28 characters and truncates a longer one with …, which is what lines the
glyphs and durations up in a column. And a duration under a minute reads as
seconds (1.02s); past that it switches to clock form, 4:07 and then
1:12:30.
What the saved figure counts
A run with cache hits ends its summary with a saved figure. It is a sum of one number per hit: how long the run that wrote that cache entry spent producing it, recorded in the entry’s metadata at the time. When the total comes to zero the tail is left off, so a run with no hits reads the way it always has.
That makes it task time, not wall clock. Four cached one-minute tasks with
nothing between them in the graph add up to 4m 00s saved, even though running
them would have cost you about a minute of waiting. It is a count of work not
repeated. The elapsed time next to it is the clock.
Past a minute the two figures are written differently. Under a minute they match
(4.27s). Above it, saved time reads 4m 07s, then 14h 22m once it crosses
an hour, dropping the seconds — where the elapsed time switches to clock form,
4:07 and then 1:12:30.
Each run’s figure is also appended to a ledger kept with the cache.
lattice stats adds them up across every run the repo has
recorded.
Read the live display
A run at a terminal, mid-flight. Every task that has started has a line, in the order they started, and a task that has already ended keeps its line where it was until the run is over:
❖ lattice build · 4 workspaces
────────────────────────────────────────────────────
⠸ docs:build running…
✓ ui:build 1.02s
⠙ api:build running…
⠋ web:build running…
ui:build sits second because it started second, not because of where it
finished. web and api, which depend on it, joined underneath once it was
done. A task’s line settles in place when it ends: ✓ and its duration on
success, ✗ FAILED with the exit code and the elapsed time on failure, a teal
● and the short cache key on a hit.
Nothing above those lines reports a hash or a cache miss. The task’s own line
already carries both. A hit prints its abbreviated key, and a miss shows up as
the task running, so the live display leaves the trace to -v and the raw
stream.
Whatever a task prints while it runs is collected rather than shown. A task that succeeds never shows its output in this mode. A task that fails gets its output printed under a header once the run reaches it:
❖ lattice build · 4 workspaces
────────────────────────────────────────────────────
● docs:build cache hit [94477519]
○ web:build skipped (dependency failed)
○ api:build skipped (dependency failed)
✗ ui:build output
ui: building 3 files
ui: cannot resolve module 'styles'
────────────────────────────────────────────────────
❖ 2 tasks · 1 cached · 1 failed 1.02s · 3.02s saved
The captured lines read in the order the task produced them, whichever stream each one came from, and they print at full brightness rather than dimmed. A compiler’s error and the file it was compiling stay next to each other, because Lattice appends every line from both pipes to one buffer as it reads them. That is arrival order as Lattice reads it. A task that dumps a whole stdout buffer and a whole stderr buffer in one burst can still print one stream before the other. Output that arrives spread over time is in the order it happened.
That is the whole screen after the run, not a frame from the middle of it.
Lattice clears the live region when the run ends, so the header, the cache hits,
the skipped tasks, the failure output, and the summary stay, and the ✓ and ✗
lines go with the region they were drawn in. The counts in the summary are the
record of what ran.
Two tasks in that summary, not four: web:build and api:build were skipped
because ui:build failed, and a skipped task is counted separately from one
that ran. See Selecting what runs for --continue,
which is what let the run get that far.
When every task came back from cache, a banner follows the summary:
❖ lattice build · 4 workspaces
────────────────────────────────────────────────────
● docs:build cache hit [94477519]
● ui:build cache hit [5341be25]
● api:build cache hit [1ccd0b3e]
● web:build cache hit [d25c0cf0]
────────────────────────────────────────────────────
❖ 4 tasks · 4 cached · 0 failed 0.00s · 8.12s saved
❖❖❖ FULL POWER
The banner needs at least one task scheduled, no failures, and a hit for every
one. A single miss, a failure, a persistent task in the graph, or a --filter
that matched no workspace all leave it out.
It says FULL POWER and not FULL CACHE. A full cache is how a disk running out
of room is described, which is the opposite of what just happened.
8.12s against an elapsed 0.00s is the task-time reading at work: those four
builds took 8.12 seconds of work between them when they last ran, spread over
about three seconds of waiting.
Read the raw stream
Pipe the same run and you get one line per event, prefixed workspace:task::
$ lattice run build | cat
ui:build: running
docs:build: running
ui:build: done (1.02s)
api:build: running
web:build: running
docs:build: done (3.02s)
web:build: done (2.04s)
api:build: done (2.04s)
lattice: 4 tasks, 0 cached, 0 failed, 3.08s
Lines arrive as events happen, so they interleave: docs:build starts second
and finishes last. Nothing repaints, nothing is erased, and every line is a
label plus plain text, which is what makes the stream safe to grep or feed to
another tool.
A run with nothing to do is one line per task, then the summary:
$ lattice run build | cat
docs:build: cache hit [94477519]
ui:build: cache hit [5341be25]
api:build: cache hit [1ccd0b3e]
web:build: cache hit [d25c0cf0]
lattice: 4 tasks, 4 cached, 0 failed, 0.01s, 8.12s saved
lattice: full power, nothing to run
lattice: full power, nothing to run is the raw form of the FULL POWER
banner, under the same rule and with no color, so a CI log can be grepped for
it.
The saved figure is appended after the elapsed time rather than inserted before it, so a log grepped for the counts reads the way it always has.
Without -v, a task’s own output is collapsed here too. A failure is the
exception: the captured lines print underneath the FAILED line, in the order
the task produced them, each still carrying the task’s label.
$ lattice run build --continue | cat
ui:build: running
docs:build: cache hit [94477519]
ui:build: FAILED (code 3) after 1.02s
ui:build: ui: building 3 files
ui:build: ui: cannot resolve module 'styles'
lattice: 2 tasks, 1 cached, 1 failed, 1.03s, 3.02s saved
code 3 is the exit code the command returned, and after 1.02s is how long it
ran. A signal or a timeout leaves the line ui:build: FAILED after 1.02s, and
a task that never got as far as running its command leaves it ui:build: FAILED.
Nothing named web:build or api:build appears. In raw mode a skipped task is
silent unless you pass -v.
Turn on the hash and cache-miss lines
-v adds three things to the raw stream: the run header, the per-task trace
lines, and every task’s own stdout and stderr as it is produced. This is the only
place the hash and cache-miss lines appear; the live display does not carry
them.
$ lattice run build -v
lattice: running `build` across 4 workspaces
lattice: docs:build: hash 944775197435b927
lattice: ui:build: hash 26be571e2ec773a7
lattice: ui:build: cache miss: inputs changed
ui:build: running
docs:build: cache hit [94477519]
ui:build: ui: built 1 file
ui:build: done (1.03s)
lattice: web:build: hash 7c91a67acc30bce0
lattice: api:build: hash c70d30d17414bd4a
lattice: web:build: cache miss: dependencies changed
web:build: running
lattice: api:build: cache miss: dependencies changed
api:build: running
web:build: web: built 1 page
api:build: api: built 1 binary
api:build: done (2.03s)
web:build: done (2.03s)
lattice: 4 tasks, 1 cached, 0 failed, 3.09s, 3.02s saved
The lattice: prefix marks a line Lattice wrote about the run rather than a
line a task wrote. Each hash line prints just before the cache lookup that
uses it, and the cache miss: line after it names which part of the key moved.
Here an edit to ui’s source moved inputs, and web and api then missed on
dependencies, because a prerequisite’s key is part of theirs. For what each
component name covers, see Cache
internals.
A failing run under -v prints the failed task’s output twice: once live, as
the process produces it, and once more in the replay that follows the FAILED
marker.
$ lattice run build --continue -v
lattice: running `build` across 4 workspaces
lattice: ui:build: hash b64234148e6c7a2d
lattice: docs:build: hash 944775197435b927
lattice: ui:build: cache miss: inputs changed
ui:build: running
docs:build: cache hit [94477519]
ui:build: ui: building 3 files
ui:build: ui: cannot resolve module 'styles'
ui:build: FAILED (code 3) after 1.03s
ui:build: ui: building 3 files
ui:build: ui: cannot resolve module 'styles'
web:build: skipped (dependency failed)
api:build: skipped (dependency failed)
lattice: 2 tasks, 1 cached, 1 failed, 1.03s, 3.02s saved
At a terminal, -v is also one of the mode triggers, so it is how you get this
stream at an interactive shell rather than the live display. In a pipe or a CI
job you are already in raw mode, and -v only adds the extra lines. See Run
Lattice in CI.
Follow one task down a screen of eight
At a terminal, raw mode colors the workspace:task label at the head of each
line, and every task in the run gets its own. Lines from tasks running in
parallel interleave, and the color is what lets you read one of them.
The palette is eight hues one 45° step apart, all at the same saturation and
lightness so no label reads as louder than another. It starts at 25° so nothing
in it can be mistaken for the red a FAILED marker uses. Colors go out in the
order labels are first seen, so the first eight distinct workspace:task pairs
never share one and a ninth wraps back to the first. Because tasks start in
parallel, which color a task gets can differ between runs; within one run it
never changes.
Both halves of the label count, so web:build, web:test, and api:build are
three colors. A trace line gets the same treatment: the web:build inside
lattice: web:build: hash … carries that task’s color.
Nothing else on the line is styled. FAILED is still the word FAILED, so no
status here is carried by color alone.
Turn color off
Color follows the terminal, not the mode. Both modes color when stdout is a real
terminal and neither does when it is not, so a -v run at your shell has
colored labels and the same run redirected to a file emits no escapes at all.
To suppress color at a terminal, set NO_COLOR to any value:
NO_COLOR=1 lattice run build -v
The layout does not change, only the escapes. Lattice decides about color once
per run, after the mode is settled and before anything prints, which is why a
persistent: true task forcing raw mode keeps its colored labels.
Interactive spends color on the teal accent for the header, the rosette, the
spinners, cache hits, and the saved figure in the summary, plus a green ✓ and
a red ✗ on results. The elapsed time beside the saved figure stays dim. Raw
colors exactly one thing: the label.
Keep progress and failures apart
In raw mode the two streams split like this:
| Line | Stream |
|---|---|
running, cache hit, done, exited (code 0), the summary |
stdout |
skipped and trace lines, both -v only |
stdout |
FAILED, EXITED (…), lattice: warning: … |
stderr |
| A task’s own output | whichever stream the task wrote it to |
So lattice run build 2>/dev/null keeps every task’s progress and its stdout
and drops the warnings, the FAILED markers, and anything a task wrote to
stderr. In interactive mode the live display goes to stdout and failure output
goes to stderr, so redirecting stdout away still leaves the failure dump on the
terminal.
Two kinds of line sit outside the per-task events. A trace line carries hashing,
cache, and toolchain detail about one task. Raw mode prints it only under -v,
and the live display does not print it at all. A note about the run as a whole is
the exception in interactive. Provisioning a toolchain and pruning the cache
print dim, because nothing else on screen would account for the wait. A warning
always prints in both modes, prefixed lattice: warning: in raw and labeled with
a yellow warn in interactive.
An invalid byte costs one character
A task’s output arrives as bytes. Lattice splits it on newlines and decodes each line as UTF-8, and anything invalid becomes the replacement character, U+FFFD. A compiler that emits a stray byte, or a tool that prints a filename in another encoding, costs you that one character and nothing else.
Lattice used to drop the rest of that task’s output at the first invalid byte. The line that explains a failure usually comes after the noise that caused it, so a failing task could report nothing at all.
A trailing \r goes with the newline, so output from a Windows tool does not
carry a \r into the display.
Related pages
- Run dev servers for the run that forces raw mode and streams as it goes.
- Run Lattice in CI for what a build machine gets and why.
- Cache internals for the component names in a
cache miss:line. - CLI reference for every flag and how a flag, an
environment variable, and a
settingskey rank against each other.