vx last
vx last replays a recorded run’s summary from the local history: no
re-execution, no cache probe, no config evaluation. A run a Ctrl-C (or
SIGTERM, SIGHUP) stopped records nothing, so vx last still shows the
run before it.
vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]Examples
Section titled “Examples”$ vx lastrun 01a16de7-6cd7-7563-984c-5ffba8fb6629 — ok $ vx run ci --all 2026-10-24T12:34:05.662Z · 61ms · master @ 1587dec6 3 tasks · 0 hits
success app#test 3ms d6e223ce81dfdb2c 1.1× cpu success app#build 7ms b63bf0705528ba99 0.5× cpu success lib#build 7ms e5123c0191687798 0.7× cpu$ vx last --listok 2026-10-24T12:34:05.662Z 01a16de7-6cd7 3 tasks · 0 hits 61ms $ vx run ci --allok 2026-10-24T12:34:05.367Z 01a16de7-6bb3 3 tasks · 0 hits 52ms $ vx run ci --allvx last --failed # the latest run that failedvx last --log app#test # one task's outputvx last --stats app#test # p50, p99, pass and hit rate over the last 50 runsArguments
Section titled “Arguments”| Argument | Meaning |
|---|---|
RUNID |
replay that run instead of the latest. A run id may be typed as a unique prefix; a prefix several runs share fails and lists them. An unknown run id fails loud and points at --list |
Options
Section titled “Options”| Option | Meaning |
|---|---|
--list[=N] |
the N most recent runs (default 10) |
--failed |
the latest run that failed, past any green one since; with --list, only failed runs |
--log <task> |
one task’s output instead of the summary |
--stats [<task>] |
per-task numbers over the last 50 runs |
--branch <name> |
the branch --stats forecasts from |
--format pretty|json |
json for scripts and agents, below |
--cache-dir <path> |
read the cache a run with the same flag uses |
--log does not combine with --list or --failed. --stats does not
combine with --list, --failed or --log; --branch combines only
with --stats.
Understanding the output
Section titled “Understanding the output”The header and rows. Bare vx last replays the most recent run: a header (verdict, command,
when, duration, branch @ sha, CI, task/hit/failure counts) and a
per-task table — status, id, duration, cache key, and for a task that
executed, what it used (peak RSS and CPU parallelism, 312 MB · 1.4× cpu: the runner’s own record, and the number @vzn/vx-history
reserves from; a hit spent nothing and shows nothing, and a task lighter
than vx itself shows its CPU only) — failures first, each as the frame
read it, failed (exit 137), with the signal an exit above 128 stands
for at the row’s end (128 + SIGKILL; the shell’s convention, so a
command that exits 137 on its own reads the same). Where the run’s
footer gave a reason, the row ends with it instead: timed out,
never ready: exited, 2 sandbox violations, and for a skipped task
after lib#build failed (the v27 columns; rows older than them read
as before). A task whose key met a remote failure ends with it, read or
write alike: remote ✗ download: connection refused; upload: 503 (the
v40 column, remoteError in --format json). A task an executor
plugin ran elsewhere carries where after its id: app#build@small#1 (v44, placement).
Order and folding. Rows run failures first, then what executed or was
skipped, then hits; past sixteen hits the rest fold into one line with
their count (… +980 more cache hits) — a thousand-task warm run is a
thousand rows otherwise, with the one failure a screen above the
prompt — and the sixteen shown are the slowest restores, the one thing
a hit’s row tells. --format json lists every row.
Steps. Below the tasks, the steps the run did not wait on (vx:prefetch, a
remote’s downloads beside the tasks), which its own output never shows:
success vx:prefetch 1.20s internal. They are kept per run, the
newest 50, in <cache dir>/steps/<run id>.json, written only by a run
that had one; --format json adds them as steps.
Re-run what failed. A replayed run with
failures ends with the command that re-runs them (re-run what failed: vx run app#test -- …, with the arguments the run forwarded to the
tasks that got them; a failed dependency, which never got them, gets a
line of its own without them, X-140). The
replayed $ vx run … or $ vx watch … line and that one keep each
argument shell-quoted (-- 'a b'), so either pastes back as it ran (X-49).
No runs yet. Before any run, every form says so
(› vx last no runs yet with fix vx run, exit 1; --list prints
› vx last no runs yet, exit 0), and past green runs only,
--list --failed prints › vx last no failed runs.
--list
Section titled “--list”vx last --list prints the N most recent runs (default 10), one per
line in aligned columns (verdict, start, run id, counts, duration,
command), each run id cut to the shortest prefix no other run shares, 13 characters at least (--list 5
and --list=5 alike; the space form takes the next argument as the
count unless it is a flag or a run id, eight hex digits at least, so
--list 1.5 is refused as a count); vx last RUNID replays a
specific one, and the two do not combine (a word beside --list no run
id could be is refused as an unexpected argument).
--log <task>
prints one task’s output instead of the summary: in the latest run that
recorded the task, or in RUNID’s. A failed task’s is the kept failure
output; any other task’s is its cache entry’s log (the one a hit
replays, masked as it was), so a passing task’s log reads too, on a hit
or in the run that saved it. An uncached task that passed, or a skip,
kept none, nor does a failed task past the newest 50 failed runs:
stdout is empty and stderr says so, exit 0. A kept log that is empty
says the task printed nothing. With
--format json it is { runId, taskId, status, source, output },
source 'failure', 'cache' or null. A task no run recorded is
refused, exit 1.
vx last app#build --log reads the same as --log app#build: a --log
with no value takes the positional as its task.
--stats
Section titled “--stats”--stats [<task>] prints per-task numbers over the last 50 runs, the
window the scheduler and --dry learn from: runs, p50 (the forecast a
run shows: the median of the task’s last five timings on the branch,
else on any branch), p99 of the executed successes, pass and hit rate, failure mode (stable,
flaky-recoverable, flaky-fatal), the largest peak RSS and the mean CPU
parallelism (a hit counts what its producing run used), one table row
per task. A task id narrows to that task, a task name to it in every
project; one the window has no run of is refused, exit 1. The branch is
--branch, else VX_RUN_BRANCH, else git’s, then the CI provider’s, as
a run’s. With --format json it is { window, branch, stats }, each row { taskId, runs, p50DurationMs, p99DurationMs, successRate, hitRate, failureMode, maxPeakRssBytes, maxCpuParallelism }, null where nothing was
measured.
--format json
emits { invocation, tasks } for scripting; --list --format json
an array of the same invocation objects, newest first. A failed task’s row adds
output (its output as plain text, the first 8 KiB and last 56 KiB,
secrets masked) and locations (the existing files it names,
{ file, line?, col? }, file absolute; a file also named with a line
drops its bare mention, such as bun test’s app.test.js: header), so an
agent reads why without the terminal. fixedIn names the first later run where that task
passed, so a replayed failure that is already fixed says so. A
sandboxed task’s row adds sandboxFix, the exec.sandbox.allow
entries (read, write, network, systemInfo) that would have
granted its denials, the same grants the terminal prints as fix:;
withheld lists the credential paths it denied and left out (a .env file, or a store under home the sandbox denies, security.md), and, for
a cached task, inputs the reads of its project no inputs.files
glob matches (the terminal’s and, if the output depends on them, …
line). A task
failed by inputStability.severity: 'error' adds inputMoved, the
input that changed during the run. They live in <cacheDir>/failures/<runId>.json, the
newest 50 failed runs; past those, or when the write was refused, a
failed row reads output: '' and locations: [].
--cache-dir
Section titled “--cache-dir”vx why, vx last, vx info and vx cache clean all read the cache
a run wrote, so each takes --cache-dir <path> with vx run’s rules
(cwd-relative, absolute used as-is): a run that wrote its history
elsewhere is replayed, explained, reported on and pruned there. Without
the flag they open the workspace’s cache (defineWorkspace({ cache: { dir } })
or .vx/cache). A --cache-dir that is not there is refused by name
(--cache-dir .vx/cahce: no such directory). In a workspace that never
ran, vx why, vx last, vx info, vx show and vx cache clean
(dry or not) read it as empty and create nothing (item 900, E-7, E-38).
Exit codes
Section titled “Exit codes”| Code | When |
|---|---|
0 |
the run (or the list) printed, --list with no runs included |
1 |
no recorded run yet, a run id it has no record of, or a parse error |