Skip to main content
So far the tasks have asked Bazel questions and read the answers. This one runs a build and reads what Bazel says while it runs. Bazel publishes an event for everything it does — targets configured, actions executed, tests finished. Most tools recover a fraction of that by scraping the terminal output. A task can subscribe to it directly.

Run it

Bazel’s own output goes by, and then:

How it works

Fifty-six lines. Open .aspect/observe.axl and read it top to bottom.
bazel rather than ctx.bazel: the iterator exists before any build does, which is the point — subscribe first, then start the build. Bazel emits events from the moment it starts; a subscription made afterwards misses them. kinds is the filter. A real build publishes thousands of events across dozens of kinds; this task wants one kind, so it asks for one kind.
The loop waits for the next event and ends when Bazel is done. No polling, no callbacks. flags is where Bazel flags go. A task’s own arguments are the ones it declares — type --nocache_test_results on the command line and the CLI will reject it, because observe never said it took such a thing. This one is built in: a cached test publishes a result without running, and there is nothing to watch in that. Each test_summary carries the label on event.id and the outcome on event.payload:
The task collects during the build and prints afterwards, so its output never lands in the middle of Bazel’s. Bazel owns the terminal until it exits.

Your turn

Run it against a test that fails sometimes:
internal/domain/forecast has a deliberately flaky test — it fails about one run in three. Run it a few times and you will see failed instead of passed. You will not see flaky. That status only exists when Bazel retries a failing test and the retry passes, and nothing here has asked it to. That is the next section. Next: you won’t ship this.