Skip to content

feat: see exactly why a build gate failed - #35

Merged
JacobAtchley merged 4 commits into
mainfrom
feat/build-failure-reason
Sep 23, 2026
Merged

JacobAtchley merged 4 commits into
mainfrom
feat/build-failure-reason

Conversation

@JacobAtchley

Copy link
Copy Markdown
Owner

Opening a failed gate's log answered that it failed, not why: the pane
opened at the top of a few thousand lines and left the reader to scroll for the
one that broke, through a tail that was still appending.

Three things, all in the build log pane:

The failure digest. Azure DevOps records an issue per error against the
task that raised it — what its own run summary shows — and boardwalk parsed the
error count while dropping the messages beside it. Records keep their issues
now, azdo.Failures folds them into the failed tasks in execution order, and
the pane pins that above the viewport. It comes off the timeline the pane
already fetched, so there is no new call, and it is there before a line of log
has loaded. Capped at six rows or a third of the pane, saying how many it hid;
a task that failed without publishing a message is still named.

n — jump to the next error. The log's own ##[error] lines, each put at
the top of the pane, wrapping at the end. No key for the previous one: N and
p both already mean something on the build list this pane opens from.

e — errors only. The error lines and the task headings that own them,
nothing that merely ran. A log with no errors is left alone rather than
emptied. Both toggles put the pane back on the line it was showing, so
filtering down to an error and back leaves you at that error in its own output.

Notes

The jump indexes the buffer by row, not by line: an endgroup marker writes no
row and a task rule writes the blank line above itself. Both write paths go
through one place that counts them, and logRows is checked against what the
writer actually emits.

A review pass over the first three commits found the jump never wrapped when
the last error sat near the end of the log — SetYOffset clamps, so the offset
read back is not the row asked for — and that the digest cut error rows to the
width but not task names, which could push the help and status lines off a
narrow terminal. Both are fixed in the last commit, with the tests that catch
them.

go test ./... and go vet ./... clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW

JacobAtchley and others added 4 commits September 22, 2026 21:58
Answering "why did the gate fail" meant opening the log and scrolling it for
the line that broke — through a tail that is still appending while you read.

The timeline already carries the answer: Azure DevOps records an issue per
error against the task that raised it, which is what its own run summary
shows, and boardwalk parsed the error count while dropping the messages
beside it. Records now keep their issues, Failures folds them into the failed
tasks in execution order, and the log pane pins that digest above the
viewport.

Pinned rather than written into the buffer: the buffer grows every three
seconds while a build tails, so a heading inside it scrolls away exactly when
it matters. It is capped at six rows or a third of the pane, whichever is
smaller, and says how many errors it left out — the log is what the reader
came for, and a digest that hid it would have answered the question by hiding
the evidence.

A task that failed without publishing an issue is still named, with its
errors empty rather than invented: what it has to say is in its log, and
leaving it out would hide the failure entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW
The digest says which task failed and what it reported; finding that in the
log itself was still a scroll through several thousand lines of the output
that came before it.

n moves the pane to the next "##[error]" line and wraps at the end. The error
is put at the top of the pane rather than in the middle, since what explains a
failure is the output under it.

There is no key for the previous error. The obvious pair, N and p, both
already mean something on the build list this pane opens from, and a key
meaning one thing on a list and another on the pane it opens is the trap
keyDiff's comment names. Wrapping is what a single key buys instead.

The jump indexes the buffer by row, which is not the line's index: an endgroup
marker writes no row and a task rule writes the blank line above itself. Both
the append path and the rebuild path now write through one place that counts
them, and logRows is checked against what the writer actually emits, because
two counts of the same thing drift silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW
A failed gate's log is a few thousand lines of output that worked, with the
handful that did not somewhere inside it. e keeps the error lines and the task
headings that own them and throws the rest away, which turns that log into the
dozen lines worth reading.

A heading whose task reported nothing is dropped too — keeping it would say a
task failed when all it did was run — and a log with no errors at all is left
alone rather than emptied, since a blank pane is not an answer.

Filtered, an appended line cannot be rendered as it arrives: whether a task
heading belongs on screen depends on lines that have not arrived yet. So the
buffer is written again instead, and lines are recorded separately from being
rendered. That is also what fixes the count: a rebuild has written everything,
so nothing is left pending, and leaving a count behind had the next append
write the tail of the buffer twice.

The footer names the two keys that answer why a build failed, and refresh once
there is a point in it. Naming the scroll and timestamp keys there as well
pushed esc and ? off the end at eighty columns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW
Review of the three commits under this branch turned up two defects in them
and one habit worth fixing.

The jump never wrapped when the last error sat near the end of the log —
which is the ordinary shape of a failed build. SetYOffset clamps to the last
scrollable row, so the offset the viewport reports back is not the row the
jump asked for, and deciding the next jump from it picked that same error
forever. It now remembers what it asked for alongside where it landed, uses
the reader's own scrolling when they have moved the pane themselves, and says
when it has come back round. The test that claimed to cover this passed only
because its log happened to carry forty lines of filler after the last error.

The digest cut its error rows to the width but rendered task names raw, and a
task is named by whatever its YAML called it. Root sizes the pane by counting
newlines, which cannot see a terminal wrapping a row, so a long name cost rows
nobody budgeted for and pushed the help and status lines off the bottom. Every
row is cut now, the hidden count says how many rows were actually hidden
rather than one more than that, and a pane too short for even one row gets no
digest at all.

The filter threw the reader back to the top on the way out, which is the
opposite of what its second press is for. The buffer now maps both ways
between rows and lines, so both toggles put the pane back on the line it was
showing. That map replaces the pending count, which no longer needs to exist:
the append writes from a local, so a count cannot survive a rebuild again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW
@JacobAtchley
JacobAtchley merged commit 2d650a7 into main Sep 23, 2026
3 checks passed
@JacobAtchley
JacobAtchley deleted the feat/build-failure-reason branch September 23, 2026 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant