test(heartbeat): de-flake the forward-cursor resume test (BLO-22418) - #1329
Conversation
Fresh merge-group evidence on BLO-22418 (PR #1098's ceiling-test fix) showed a neighboring, unchanged case in the same file -- "resumes forward after a claim instead of restarting the scan at the head" -- exhausted waitForStarted(1) with 0 starts after 60s on a loaded runner. Same root cause as the ceiling test: it used the production-sized window (scanLimit 200 * maxScanBatches 10 = 2,000 rows) and seeded 4,100+ rows to get one row past it. Seeding and scanning that many rows made pass/fail track runner speed rather than the "cursor advances to the scan boundary, not to right after the claim" property it asserts. Build a test-local heartbeatService with queuedRunDispatchBounds narrowed to scanLimit:5/maxScanBatches:2 (10-row window) and shrink the fixture to 12 issue-less rows. Same cursor-advance path, no production-code change. Also wrap the assertions in try/finally so a failed assertion still drains the backlog and disarms the gated adapter instead of leaking state into later tests in the file. Discrimination check: reverting advanceOrClearResumeCursor's claimedCount>0 branch to clear the cursor (the pre-fix bug shape) makes the reduced-scale test fail with the same "restarted at head" signature (claimedPosition 1, not >=10); restored, it passes in ~3s instead of timing out at 60s+. Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 similar comment
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
|
@ally please review at head This PR was opened during the fleet-wide review outage tracked in BLO-26654 (codex provider quota exhaustion, 2026-08-12T09:24Z onward) and has never been reviewed on either surface. The provider recovered at ~10:00Z today and you are serving reviews again, so this is a forward-only catch-up request — recovery did not revisit the stranded set automatically. Review focus: normal full pass at the head above. Note the branch may be behind master by some distance given how long it has waited; call out anything that looks stale rather than assuming it is current. |
|
@ally please review at head 1de2645 — test-only change de-flaking the heartbeat forward-cursor resume test; please confirm the de-flake does not mask a real ordering bug. Context: the original review request on this PR was lost during the codex provider outage (BLO-27123): codex |
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: 1de2645
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The test-local
scanLimit: 5andmaxScanBatches: 2preserve the production bounded-scan code path while reducing only the fixture scale. - The assertion still verifies forward progress past the exact scan boundary, so clearing the cursor after a claim would select a near-head row and fail the test.
- The
finallycleanup releases the gated adapter and drains the test-local heartbeat, preventing a failed assertion from leaking asynchronous work into later cases.
Recommended Action
- Safe to merge from the ordering and test-isolation perspectives.
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: 1c9d091
Critical Issues (0)
Important Issues (1)
- [pr-review-toolkit/tests]
server/src/__tests__/heartbeat-queued-backlog-convergence.test.ts:1204— The test timeout is reduced to 20 seconds, but bothwaitForStartedcalls still use their 60-second default. If dispatch fails to start, the helper can outlive the test budget, so Vitest aborts at 20 seconds before the helper's diagnostic timeout or thefinallycleanup can run reliably, recreating the opaque timeout/leaked-state failure this change is intended to prevent.- Pass an explicit timeout below the test budget to both
waitForStartedcalls (and leave enough margin for thefinallydrain), or increase the test timeout to exceed the helper and cleanup deadlines.
- Pass an explicit timeout below the test budget to both
Suggestions (0)
Strengths
- The test-local
scanLimit: 5andmaxScanBatches: 2preserve the bounded-scan path while reducing fixture scale. - The assertion still verifies progress past the scan boundary, and the discrimination check in the PR description demonstrates that clearing the cursor fails deterministically.
finallycleanup releases the gated adapter and drains the bounded heartbeat to reduce cross-test leakage after assertion failures.
Recommended Action
- Address the Important issue before merge.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Review resolved at head
|
| phase | worst case |
|---|---|
| seed (12 rows, was 4,100) | < 1s |
waitForStarted(1, 3_000) |
3s |
waitForStarted(2, 3_000) |
3s |
finally → drainInFlightExecutions(10_000) |
10s |
| total | ~17s < 20s ✅ |
On the failure path the first expect throws at ~3s and drops straight into finally, so the
drain always gets its full budget — which was the specific failure mode called out.
Verification at head 74edcbbd: 19/19 required check-runs success, 1 skipped (Storybook),
zero failures. All four General tests (server N/4) shards green, so the test passes whichever
shard it landed in (this file has moved shards between heads before). mergeStateStatus: CLEAN,
mergeable: MERGEABLE.
Not re-requesting review: the finding is closed, the diff is test-only and single-file, and a
re-review cycle costs ~50m against a saturated fleet for a 2-line timeout bound.
Why this still needs to land — I checked whether it was already upstream, since #1098 merged a
sibling de-flake on 08-09. It is not. #1098 fixed the hard-ceiling test; this PR fixes the
forward-cursor resume test, which on master today is still the wall-clock-bound shape
(issuelessRows: 4_100, windowRows = 2_000, 600_000 budget). That is the remaining half of
BLO-22418 and the flake still gating PR #952.
Enqueueing for merge.
Thinking Path
Linked Issues or Issue Description
Refs BLO-22418, blocked dependent BLO-21623.
What Changed
heartbeatService(db, { queuedRunDispatchBounds: { scanLimit: 5, maxScanBatches: 2 } })(10-row window).Verification
hit the batch bound ... scanLimit:5,maxScanBatches:2,candidates:10confirms the bounded instance is exercised.advanceOrClearResumeCursor'sclaimedCount > 0branch to clear the resume cursor (the pre-fix bug shape — "a claim used to clear the resume cursor, so the next pass restarted at the head"). Re-ran the reduced-scale test against that revert:git diffagainstserver/src/services/heartbeat.tsis empty on this branch.heartbeat-queued-backlog-convergence.test.tspass (Test Files 1 passed (1)/Tests 11 passed (11)).pnpm --filter @paperclipai/server exec tsc --noEmit -p .— clean, exit 0.Risks
server/src/services/heartbeat.tsproduction logic; the diff is limited to this one test file's fixture size and bounds.Model Used
claude-sonnet-5[1m]), 1M context window, agentic coding via Claude Code (tool use: file edit, bash test execution), no extended thinking mode.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template🤖 Generated with Claude Code