You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
packages/loopover-miner/lib/live-issue-snapshot.js's fetchLiveIssueSnapshot is a single, bounded (timeout-only,
no retry) GraphQL round-trip to GitHub. It has exactly two callers in the package:
packages/loopover-miner/lib/claim-conflict-resolver.js's resolveClaimConflict (the POST-submission check, feat(miner): add retry/backoff to claim-conflict-resolver's post-submission live-state check #6058): wraps the call in a bounded retry loop (DEFAULT_SNAPSHOT_MAX_ATTEMPTS = 3 with defaultRetryBackoffMs exponential backoff, lines 83-106) specifically because, per its own header comment,
"a competing PR that exists but hasn't yet propagated through GitHub's own search/GraphQL indexing in the
first instant would be invisible to a single check."
packages/loopover-miner/lib/submission-freshness-check.js's checkSubmissionFreshness (the PRE-submission
check, feat(miner): add a late-binding freshness check against live repo state before open_pr fires #3007): calls fetchLiveIssueSnapshot exactly ONCE (line 59), with no retry of any kind. Its own header
comment documents "FAIL CLOSED: an unreachable/failed live-state fetch is treated as stale (aborts)" — a
single transport failure or transient GitHub error (a 5xx, or the exact index-propagation-lag scenario feat(miner): add retry/backoff to claim-conflict-resolver's post-submission live-state check #6058
was built to handle for the OTHER caller of this same function) causes checkSubmissionFreshness to abort the
ENTIRE candidate right before open_pr, discarding whatever create/iterate work already happened for it.
The fail-closed OUTCOME itself (never proceed on unavailable live state) is intentional and correct, and this
issue does not change it. The gap is that checkSubmissionFreshness gives a transient failure zero chance to
resolve itself before failing closed, unlike its sibling caller of the identical fetchLiveIssueSnapshot
function, which already established (in #6058) that a bounded retry-with-backoff is the right way to ride out
exactly this kind of transient unavailability before committing to a decision. The cost of getting this wrong is
also higher here than in the post-submission case: aborting pre-submission discards an entire completed
create/iterate loop's local work, not just a soft-claim election.
Requirements
checkSubmissionFreshness (packages/loopover-miner/lib/submission-freshness-check.js) MUST retry a failed/
null fetchLiveIssueSnapshot result with bounded attempts and backoff before treating it as "live_state_unavailable", mirroring claim-conflict-resolver.js's existing resolveClaimConflict retry
shape: a maxAttempts (default matching claim-conflict-resolver.js's DEFAULT_SNAPSHOT_MAX_ATTEMPTS = 3),
an injectable sleepFn, and an injectable backoffMs (defaulting to http-retry.js's defaultRetryBackoffMs, the same function claim-conflict-resolver.js already reuses rather than
reimplementing).
The retry loop MUST stop early and proceed the instant a real (non-null, well-formed) snapshot is obtained —
it must not always burn every attempt.
After all attempts are exhausted with no usable snapshot, the function MUST still fail closed exactly as
today ("live_state_unavailable", fresh: false) — this issue only widens the window before that fail-closed
decision, it does not change or weaken it.
The new maxAttempts/sleepFn/backoffMs MUST be optional parameters on checkSubmissionFreshness's
existing deps/options shape (not required, and not changing the function's existing required-dependency
validation for claimLedger/fetchLiveIssueSnapshot/eventLedger), so every existing caller continues to
work unchanged with sane defaults.
Deliverables
checkSubmissionFreshness in packages/loopover-miner/lib/submission-freshness-check.js retries a
failed/null fetchLiveIssueSnapshot call with bounded attempts + backoff before aborting as live_state_unavailable.
A test asserting a fetchLiveIssueSnapshot that fails on its first call but succeeds on a later attempt
(within the retry budget) results in { fresh: true } (or the correct non-live_state_unavailable
outcome), not a premature abort.
A test asserting a fetchLiveIssueSnapshot that fails on every attempt still results in { fresh: false, reason: "live_state_unavailable" } after the bounded retries are exhausted, and that the
abort audit event is still appended exactly once (not once per failed attempt).
Test Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch in submission-freshness-check.js, including the
retry-succeeds-mid-loop, retry-exhausted, and default-parameter branches, plus the two regression tests above
using an injected sleepFn (never a real timer) so the test suite stays fast and deterministic.
Expected Outcome
checkSubmissionFreshness rides out the same class of transient GitHub unavailability (a brief 5xx, or
GraphQL-index propagation lag) its sibling claim-conflict-resolver.js already retries for, instead of
discarding a fully-completed candidate's create/iterate work on a blip that a bounded retry would have resolved
— while still failing exactly as closed as before once the retry budget is genuinely exhausted.
Links & Resources
packages/loopover-miner/lib/submission-freshness-check.js:57-65 — the single, non-retried call to change.
Context
packages/loopover-miner/lib/live-issue-snapshot.js'sfetchLiveIssueSnapshotis a single, bounded (timeout-only,no retry) GraphQL round-trip to GitHub. It has exactly two callers in the package:
packages/loopover-miner/lib/claim-conflict-resolver.js'sresolveClaimConflict(the POST-submission check,feat(miner): add retry/backoff to claim-conflict-resolver's post-submission live-state check #6058): wraps the call in a bounded retry loop (
DEFAULT_SNAPSHOT_MAX_ATTEMPTS = 3withdefaultRetryBackoffMsexponential backoff, lines 83-106) specifically because, per its own header comment,"a competing PR that exists but hasn't yet propagated through GitHub's own search/GraphQL indexing in the
first instant would be invisible to a single check."
packages/loopover-miner/lib/submission-freshness-check.js'scheckSubmissionFreshness(the PRE-submissioncheck, feat(miner): add a late-binding freshness check against live repo state before open_pr fires #3007): calls
fetchLiveIssueSnapshotexactly ONCE (line 59), with no retry of any kind. Its own headercomment documents "FAIL CLOSED: an unreachable/failed live-state fetch is treated as stale (aborts)" — a
single transport failure or transient GitHub error (a 5xx, or the exact index-propagation-lag scenario feat(miner): add retry/backoff to claim-conflict-resolver's post-submission live-state check #6058
was built to handle for the OTHER caller of this same function) causes
checkSubmissionFreshnessto abort theENTIRE candidate right before
open_pr, discarding whatever create/iterate work already happened for it.The fail-closed OUTCOME itself (never proceed on unavailable live state) is intentional and correct, and this
issue does not change it. The gap is that
checkSubmissionFreshnessgives a transient failure zero chance toresolve itself before failing closed, unlike its sibling caller of the identical
fetchLiveIssueSnapshotfunction, which already established (in #6058) that a bounded retry-with-backoff is the right way to ride out
exactly this kind of transient unavailability before committing to a decision. The cost of getting this wrong is
also higher here than in the post-submission case: aborting pre-submission discards an entire completed
create/iterate loop's local work, not just a soft-claim election.
Requirements
checkSubmissionFreshness(packages/loopover-miner/lib/submission-freshness-check.js) MUST retry a failed/null
fetchLiveIssueSnapshotresult with bounded attempts and backoff before treating it as"live_state_unavailable", mirroringclaim-conflict-resolver.js's existingresolveClaimConflictretryshape: a
maxAttempts(default matchingclaim-conflict-resolver.js'sDEFAULT_SNAPSHOT_MAX_ATTEMPTS = 3),an injectable
sleepFn, and an injectablebackoffMs(defaulting tohttp-retry.js'sdefaultRetryBackoffMs, the same functionclaim-conflict-resolver.jsalready reuses rather thanreimplementing).
it must not always burn every attempt.
today (
"live_state_unavailable",fresh: false) — this issue only widens the window before that fail-closeddecision, it does not change or weaken it.
maxAttempts/sleepFn/backoffMsMUST be optional parameters oncheckSubmissionFreshness'sexisting
deps/options shape (not required, and not changing the function's existing required-dependencyvalidation for
claimLedger/fetchLiveIssueSnapshot/eventLedger), so every existing caller continues towork unchanged with sane defaults.
Deliverables
checkSubmissionFreshnessinpackages/loopover-miner/lib/submission-freshness-check.jsretries afailed/null
fetchLiveIssueSnapshotcall with bounded attempts + backoff before aborting aslive_state_unavailable.fetchLiveIssueSnapshotthat fails on its first call but succeeds on a later attempt(within the retry budget) results in
{ fresh: true }(or the correct non-live_state_unavailableoutcome), not a premature abort.
fetchLiveIssueSnapshotthat fails on every attempt still results in{ fresh: false, reason: "live_state_unavailable" }after the bounded retries are exhausted, and that theabort audit event is still appended exactly once (not once per failed attempt).
Test Coverage Requirements
99%+ Codecov patch coverage on every changed line and branch in
submission-freshness-check.js, including theretry-succeeds-mid-loop, retry-exhausted, and default-parameter branches, plus the two regression tests above
using an injected
sleepFn(never a real timer) so the test suite stays fast and deterministic.Expected Outcome
checkSubmissionFreshnessrides out the same class of transient GitHub unavailability (a brief 5xx, orGraphQL-index propagation lag) its sibling
claim-conflict-resolver.jsalready retries for, instead ofdiscarding a fully-completed candidate's create/iterate work on a blip that a bounded retry would have resolved
— while still failing exactly as closed as before once the retry budget is genuinely exhausted.
Links & Resources
packages/loopover-miner/lib/submission-freshness-check.js:57-65— the single, non-retried call to change.packages/loopover-miner/lib/claim-conflict-resolver.js:83-106(resolveClaimConflict) — the existing siblingretry pattern (feat(miner): add retry/backoff to claim-conflict-resolver's post-submission live-state check #6058) to mirror.
packages/loopover-miner/lib/http-retry.js(defaultRetryBackoffMs) — the shared backoff function bothcallers should share.
packages/loopover-miner/lib/live-issue-snapshot.js— the sharedfetchLiveIssueSnapshotboth callers use.