fix(tracker): fall back to an older log attempt, stop blaming the adapter for forge 5xx #268
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!268
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/259"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #259
labctl pr logswas unusable on runs retried during a forge outage. The Forgejo log adapter probesattempt/{k}/logsand folded any non-200/404 answer intoErrLogAdapterMismatch— "lab's Forgejo log adapter does not match this forge version". On those runs the forge has no stored log blob for attempt 2 and 500s on its route, while attempt 1 serves200 text/plainwith the full log. So a recoverable read failed, and the message pointed at a Forgejo version incompatibility that never existed. A lander on a container without the project toolchain then had no path to triage a red check at all.What changed
Fallback (adapter). A 5xx no longer aborts the probe — the first 404 remains the single terminator — and the newest attempt that answered
200 text/plainis served. Keeping the probe going is what lets an attempt-3 log still win over a broken attempt 2.Provenance through the seam.
Tracker.CheckLognow returnsCheckLogResult{Log, Attempt, FallbackFrom, FallbackStatus}instead of a bare[]byte, so "which attempt is this?" survives to the caller. The handler turns a fallback into a repeatedX-Lab-Log-Noticeresponse header;labctl pr logsprints it to stderr:The log body is untouched — stdout stays the byte-clean pipe/grep surface ADR-0060 and ADR-0032 pin. The forge-supplied check name is scrubbed of control bytes and length-bounded where the header value is built, so a check name carrying CR/LF cannot smuggle a header.
Honest classification. New sentinel
tracker.ErrLogUpstream— "the forge failed to serve this log" — names the requested route and the raw upstream status.ErrLogAdapterMismatchnarrows to genuine shape surprises: a 200 that is nottext/plain, a 404 on attempt 1, atarget_urlthat stops matching the Actions job shape, any other unexpected status, the probe cap. Both map to 502 with their message verbatim, so the CLI keeps them apart by text and sends the reader at the right system.Redaction is unchanged — fallback-served logs take the same fail-closed route, no new byte path.
Acceptance criteria
--checkforms —TestPRLogsCheckFallbackNotice,TestPRLogsDefaultFallbackNoticesTestCheckLog_everyAttemptServerErrorIsUpstream,TestCheckLog_serverErrorThen404IsUpstream,TestPRLogsUpstreamError(handler and labctl)ErrLogAdapterMismatchwith the existing actionable message — 404-on-attempt-1, non-text/plain200, 403, unmatchedtarget_url, probe cap;TestPRLogsAdapterMismatchat the CLIAmended by issue #259), and the three pin sentences the amendment falsified are corrected in placeTestPRLogsFallbackRedactionVerification
gofmt -l .clean ·go build ./...·go vet ./...·go test -count=1 ./...fully green ·golangci-lint run→ 0 issues.Out of scope (per the issue)
--attempt <n>, retry/backoff beyond the attempt fallback, GitHub backend log support, session-cookie auth, and the forge-side missing-blob cause.🤖 Generated with Claude Code
`labctl pr logs` died on runs retried during a forge outage: the Forgejo log adapter probes `attempt/{k}/logs` and folded ANY non-200/404 answer into `ErrLogAdapterMismatch` ("lab's Forgejo log adapter does not match this forge version"). On those runs the forge has no stored log blob for attempt 2 and 500s, while attempt 1 serves 200 with the full log — so a recoverable read failed, and the message sent the reporter hunting a version incompatibility that did not exist. A lander with no local toolchain then had no way to triage a red check at all. Two changes, both pinned by an ADR-0060 amendment: - Fallback. A 5xx no longer aborts the probe (the first 404 is still the single terminator); the newest attempt that answered `200 text/plain` is served. `Tracker.CheckLog` returns `CheckLogResult{Log, Attempt, FallbackFrom, FallbackStatus}` so the provenance survives the seam: the handler turns it into a repeated `X-Lab-Log-Notice` response header and `labctl pr logs` prints it to stderr. The log body is untouched — stdout stays the byte-clean pipe/grep surface — and the notice's forge-supplied check name is sanitized and bounded before it reaches the wire. - Honest classification. A new `tracker.ErrLogUpstream` carries "the forge failed to serve this log", naming the route and the raw upstream status; `ErrLogAdapterMismatch` narrows to genuine shape surprises (non-text/plain 200, 404 on attempt 1, an unmatched target_url, other statuses, the probe cap). Both map to 502 with their message verbatim, so the CLI keeps them apart by text and points the reader at the right system. Redaction is unchanged: fallback-served logs take the same fail-closed route. Closes #259 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>[autoland] verdict: pass