Repository navigation
feat: see exactly why a build gate failed - #35
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.Failuresfolds them into the failed tasks in execution order, andthe 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 atthe top of the pane, wrapping at the end. No key for the previous one:
Nandpboth 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
logRowsis checked against what thewriter 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 —
SetYOffsetclamps, so the offsetread 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 ./...andgo vet ./...clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01FNL3wKwEK6hA6kLt5aiDEW