fix(github): retry an evidence-fetch failure on any surface, not just comments (BLO-28968) - #1470
Merged
Merged
Conversation
… comments (BLO-28968) `classifyReviewerEvidenceError` classified `reviews_*` errors by HTTP status, so a bare 403/404/401 on the reviews surface reached `failPermanentDelivery` and permanently dropped the gate-status delivery. A required context that is never posted reads "Expected — waiting for status" and blocks the PR indefinitely, so this was a silent stall needing a human. The asymmetry was backwards. BLO-28920 made `comments_*` retryable because an evidence-fetch failure is not evidence about the PR — but a `reviews_*` failure means the predicate read *nothing*, strictly less information than a `comments_*` failure, which by construction only happens after the reviews surface has already been read conclusively. The surface we knew least about was the one that failed permanently. State the rule once as the default instead of per-surface: only the two enumerated configuration faults (`no_bot_login`, and `no_token` with credentials absent) are permanent; every transport error and HTTP status on every present or future evidence surface retries, bounded by MAX_ATTEMPTS. A surface added later cannot inherit the permanent-failure default by omission. This drops the per-surface `startsWith` carve-out and the file-local `isRetryableGithubHttpStatus`, which is now unreachable.
Author
1 similar comment
Author
Author
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: d745681
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The classifier now expresses the fail-safe invariant once: evidence-fetch failures retry by default, while the two explicit configuration faults remain permanent.
- The direct unit tests cover the changed HTTP cases, credential configuration behavior, and the delivery-level 404 retry path.
- The existing bounded retry policy prevents a persistent GitHub failure from creating an unbounded loop.
Recommended Action
- Merge when the remaining CI and repository checks are green.
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.
Thinking Path
Linked Issues or Issue Description
Fixes: BLO-28968 (https://paperclip.blockcast.net/BLO/issues/BLO-28968)
Refs BLO-28920 — #1422, which established the invariant for
comments_*; this PR generalises it. The defect is pre-existing and was found during pre-landing review of that PR, deliberately not bundled into it.No duplicate or related open PR: searched
Blockcast/paperclipopen PRs forclassifyReviewer,evidence,outbox,reviews_http,retryable, and28968— the only outbox-adjacent PRs are #1321 (plugin outbox FIFO) and #1243 (comment durable effects), neither touching this classifier.What Changed
server/src/services/github-status-delivery-outbox.ts—classifyReviewerEvidenceErrornow returnsretryable: truefor every error except the two enumerated configuration faults (no_bot_login;no_tokenwith credentials absent). Drops the per-surfacestartsWith("comments_")carve-out and the HTTP-status branch.isRetryableGithubHttpStatus. (The identically-named helper ingithub-app-auth.tsis untouched and still classifies HTTP failures at the fetch layer, where a status is the right signal.)_classifyReviewerEvidenceError, following the existing_resetInstallationTokenCacheconvention, so the rule can be asserted directly rather than only through a delivery round-trip.server/src/__tests__/github-status-delivery-outbox.test.ts— six unit tests pinning the rule (3 retryablereviews_http_*, 3 unchanged config outcomes) in a new database-freedescribe, plus one delivery-level test that areviews404 leaves the rowqueuedwithnextAttemptAtset.Behaviour delta is exactly the intended one. Walking every string
githubHasReviewerEvidenceForPrcan return:no_bot_login,no_token,bot_login_not_app_form,reviews_rate_limited,reviews_pagination_exhausted,reviews_fetch_failed,comments_*, and retryable-statusreviews_http_{408,409,429,5xx}are all unchanged. Only non-retryable-statusreviews_http_*(403 without a rate-limit signal, 404, 401) changes — from permanent drop to bounded retry.Verification
pnpm exec vitest run server/src/__tests__/github-status-delivery-outbox.test.ts— 23 passed (23), embedded Postgres available so the delivery-level test really ran:Negative control — the same tests against the pre-fix classifier (source reverted, test-only export kept) fail 4/23, proving they bite rather than passing vacuously:
The three config-behaviour tests passed before the fix too — that is the point of including them: they guard AC3 against regression rather than asserting the change.
Also green:
npx tsc --noEmit -p server/tsconfig.json(exit 0) andnode scripts/check-test-undefined-symbols.mjs("ok no undefined identifiers in server tests").Risks
Low, and asymmetric in the safe direction.
MAX_ATTEMPTSis 5 with 30s/2m/10m/30m/2h backoff, so the worst case for a genuinely persistent 403 is 5 evidence reads over ~2.7h, then a terminalfailed— no unbounded loop, and no new external writes (the evidence read is a GET).failed_permanent→failed. Both are terminal and neither posts the status, so the PR-facing behaviour is identical; onlylastErrorKinddiffers in the delivery row and its run event.handleFreshCommitStatusIfPresentstill routes a non-retryable commit-status-read failure tofailPermanentDeliveryviagithubGetLatestCommitStatusForContext. That is a different surface answering a different question, and BLO-28968 scopes itself to the evidence classifier. Worth a look separately; not bundled here, for the same reason this was not bundled into fix(review): accept COMMENTED exact-head App review as run-output attestation (BLO-28920) #1422.bot_login_not_app_formremains retryable. It is arguably a permanent misconfiguration, but it was retryable before this change too, so leaving it is the status quo and the fail-safe direction. Widening the permanent set is exactly the hazard this PR removes.Model Used
Claude Opus 4.5 (
claude-opus-5[1m], 1M context) with extended thinking, via Claude Code with tool use — driven by the Paperclip CTO agent.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template