Skip to content

vp run: a live log of run events, so a failure can be acted on while the run continues (today nothing names a failure until the run ends) #923

Open

Description

@jasonkuhrt

Written by an AI agent (Claude) on behalf of @jasonkuhrt.

Summary

vp run reports that a task failed only when the whole run ends, and only in its summary. Nothing a person or a program reads during the run says which task failed. This asks for a log of run events that vp run writes as the run goes, one JSON line per event, where each finished task points at a file holding its own output.

It came up on the --continue PR, #898: with --continue a run keeps going after a failure, but the first sign of that failure arrives only at the end.

What happens today

No log mode prints anything when a task exits non-zero. Each mode's finish ignores the exit status (interleaved, labeled, grouped), and only the summary records it.

In #898's own snapshot (continue_keeps_running_independent_tasks.md), failing exits 42 while the other tasks keep running:

~/packages/pkg-c$ vtt barrier ../../.barrier sync 2 --exit=42
~/packages/pkg-c$ vtt exit 0

~/packages/pkg-c$ vtt barrier ../../.barrier sync 3

~/packages/pkg-c$ vtt barrier ../../.barrier sync 3


---
vt run: 0/4 cache hit (0%), 3/4 successful. (Run `vt run --last-details` for full details)

No line names failing or its exit code. The summary says one task failed once every task has finished. Naming it takes --last-details, which reads last-summary.json, written when the run ends (session/mod.rs).

Without --continue, the run exits right after the failure, but still names nothing. In #822's example the summary reads 0/4 cache hit (0%), 4 failed., counting the three tasks it killed.

Why it matters

  • Automation sees a failure late, or loses work. An agent or a CI step that wants to start on the first failure has two choices today. Fail-fast exits early, but kills the tasks in flight and drops their cache entries. --continue keeps them, but says nothing until the end. In a 10-minute run of 100 tasks, a failure at minute 3 waits 7 minutes to be seen.
  • A failed task's output is hard to find. In the default interleaved mode, the output of tasks running at the same time is mixed together. To read one task's error, a reader goes through the whole run's output. For an agent, that's the whole run in tokens.
  • Skipped tasks aren't shown. With --continue, a task that depends on a failure doesn't run and isn't listed. feat(run): keep running independent tasks after a failure with --continue #898 left listing them for a follow-up, because it needs a reporter and summary format.
  • JSON output has no shared shape yet. Preview cache hits without running tasks #791's dry run left JSON output for later, and docs: add RFC for task listing without no-interactive (--no-interactive and --json flags) #320 proposes --json for task listing. A run's events would be a third per-task JSON record.

Prior art

Tool What it writes
Bazel --build_event_json_file=<path> writes the Build Event Protocol as JSON, readable while the build runs. A test's testResult event lists its test.log by file:// URI instead of embedding the output (docs, a filter for failed tests' logs).
Cargo --message-format=json prints one JSON object per line on stdout during the build: compiler-message, compiler-artifact, build-script-executed, build-finished (docs).
Go go test -json streams test events on stdout as tests run (start, run, pass, fail, skip, output, and more), with the tests' output carried in output events (docs).
Turborepo --summarize writes one JSON file to .turbo/runs/ when the run ends. It isn't a stream.

Proposal

A run event log. On every run, vp run appends one JSON line per event to a file in the cache directory, beside last-summary.json, flushing each line as it's written. For #822's example, with an e#check that depends on a#check, run with --continue:

{"event":"run_started","tasks":["a#check","b#check","c#check","d#check","e#check"]}
{"event":"task_started","task":"a#check","cache":"miss"}
{"event":"task_started","task":"b#check","cache":"miss"}
{"event":"task_finished","task":"a#check","exit_code":1,"duration_ms":2000,"cache_updated":false,"log":"<cache>/logs/a#check.log"}
{"event":"task_skipped","task":"e#check","waiting_on":"a#check"}
{"event":"task_finished","task":"b#check","exit_code":0,"duration_ms":8000,"cache_updated":true,"log":"<cache>/logs/b#check.log"}
{"event":"run_finished","exit_code":1}

The field names are a sketch. task_finished could carry the TaskResult that last-summary.json already stores for each task, so the log and the summary share one record.

A file for each task's output. Every cached task's output is already piped, so it can be stored in the cache (reporter/mod.rs). vp run also writes it to a log file and names that file in the task's task_finished event. Unlike go test -json, the output stays out of the events: a reader goes through one line per task, then opens only the logs of the tasks that failed.

--json. Streams the same lines to stdout. Task output stays in the log files, so nothing else reaches stdout, which is the rule in #3.

A failure line in the terminal. The human reporters print a line when a task fails, for example ✗ a#check (exit code: 1). It's the terminal's rendering of a failed task_finished.

--last-details reads the event log, so it also works while a run is going.

With these, an agent can start a run and pick up each failure as it lands, while the rest of the run finishes and is cached:

vp run -r --continue check &
tail -f node_modules/.vite/task-cache/last-run.jsonl | jq -c 'select(.exit_code > 0)'

Open questions

  • Nested and concurrent runs. A task whose script runs vp run starts a run of its own. Its events could go into the parent's log, under that task, the way feat(run): keep running independent tasks after a failure with --continue #898 passes --continue on to a nested run. Two runs started side by side in one workspace need a file each, and a way to find the latest.
  • Retention. Logs of the last run only, or of the last few.
  • Tasks with inherited stdio. A task whose output isn't captured has no log file, so its log is null.
  • File names. A task name like @scope/pkg#build contains a /.

Non-goals

How we hit it

We run vp run -r over a monorepo of about 80 packages, and we want a coding agent that started it with --continue to begin on the first failure while the rest of the run finishes and is cached. Today the agent learns of the failure only when the run ends, then searches the whole run's output for that task's error.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions