Skip to content

test(productivity-review): cap the full-window recovery test like its siblings (BLO-22985) - #1911

Merged
allyblockcast[bot] merged 1 commit into
masterfrom
staff/blo-22985-productivity-review-window-cap
Sep 18, 2026
Merged

allyblockcast[bot] merged 1 commit into
masterfrom
staff/blo-22985-productivity-review-window-cap

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • The productivity-review service files and reconciles reviews of agent runs; its recoverStaleReservedProductivityReviews path is covered by three tests that saturate a whole scan window (250 rows) to prove starvation bounds
  • Two of those three carry an explicit }, 120_000) vitest cap. The third — the BLO-33477 full-window recovery test — was authored without one, so it silently inherited the 60s global in server/vitest.config.ts
  • It costs 28.9s measured, i.e. only ~2.1x under that global, while every other test in the same 184-test file has 13x or more headroom. On a merge-queue runner pod pinned to 1.0–1.8 cores that margin does not survive
  • It did not: run 35226379587 timed it out at exactly 60000ms and ejected #1854 from the master merge queue on 2026-09-17, restaging the batch behind it
  • This pull request gives that one test the same 120s cap its two siblings already have, with the measurement and the rationale recorded inline
  • The benefit is one fewer fixed number in the server suite that is tight enough to convert CI load into a queue restage for the whole fleet

Linked Issues or Issue Description

What Changed

  • server/src/__tests__/productivity-review-service.test.ts: added }, 120_000) to recovers a stale reservation behind a full window of failing ones (BLO-33477), matching the two sibling full-window tests at the same cohort size, plus an inline comment recording the measured cost, the margin, and why 120s still catches the regression.
  • No production source, no assertion, no literal, and no cohort size changed. The diff is the timeout argument and a comment.

Verification

  • Ran the test in isolation against embedded Postgres: ✓ ... (BLO-33477) 32471ms, 1 passed. Ran the whole file: 184 tests, all green, this test 28932ms.

  • Ranked every test in the file by measured duration. This one is 3.1x the next slowest (9335ms, already capped at 120s) and is the only test in the file with under 3x margin against its effective budget — so no other test here needs widening:

    test measured budget margin
    recovers a stale reservation behind a full window (BLO-33477) 28.9s 60s (global) 2.1x
    reaches a recently-updated stalled issue (BLO-30303) 9.3s 120s 12.9x
    re-reaches an already-scanned issue (BLO-30303) 8.4s 120s 14.3x
    retires a review outside one retirement-scan window (BLO-33477) 2.7s 120s 44x
    next slowest (BLO-26165) 4.5s 60s 13.3x
  • Host: 64 cores, load average ~10, embedded Postgres. A CI runner pod at 1.0–1.8 cores is the slower end of that.

Risks

  • The invariant could be loosened. It is not: the failure this test detects is an unbounded recovery loop (pre-fix, the failing cohort keeps its original reservedAt, stays ahead of the target forever, and targetFinalizeAttempts never reaches 1). That does not finish at 60s, at 120s, or at any budget — it fails on the assertion, not the clock. 120s only buys room for the honest 29s cost.
  • 120s could itself be too tight later. It is 4.1x the measured cost, versus the 2.1x that just failed. If the cohort size or per-row cost grows materially, this needs re-measuring rather than re-widening.
  • Falsification, per the issue's own Verifying signal: if this test times out in merge_group after this lands, the load-flake classification is wrong and it should be reopened as a real defect in the recovery path — not widened a second time.

Model Used

  • Anthropic, claude-opus-5 (Claude Code)

… siblings (BLO-22985)

The BLO-33477 full-window recovery test was authored without an explicit
vitest cap, so it inherited the 60s global while its two sibling
250-cohort tests in the same file carry 120s. It inserts 500 rows and
then drives 250 deterministically-failing finalizes, each with its own
back-off UPDATE: measured 32.5s unloaded, i.e. 1.8x under the global.
A merge-queue runner pod at 1.0-1.8 cores clears that margin easily and
did, ejecting #1854 from the master queue on 2026-09-17 (run 35226379587).

120s is 3.7x the measured cost. It does not loosen the invariant: the
failure this test detects is an unbounded recovery loop, which does not
finish at any budget.
@allyblockcast

allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-22985
🔗 Paperclip issue: BLO-30303
🔗 Paperclip issue: BLO-26165
🔗 Paperclip issue: BLO-33477

1 similar comment
@allyblockcast

allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-22985
🔗 Paperclip issue: BLO-30303
🔗 Paperclip issue: BLO-26165
🔗 Paperclip issue: BLO-33477

@github-actions

Copy link
Copy Markdown

@ally head 25cb496 has been awaiting review for 1.7h with no review on either surface (pulls/1911/reviews carries no consolidated report for this head, no ## Ally comment either) -- automated sweep (BLO-22892 / BLO-28203), not a human/agent re-ask.

Requested a review from @allyblockcast directly (native GitHub review request, not just this comment) against current head 25cb496.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex (CLI unavailable in this k8s runtime; prompts applied directly to the diff and the exact changed path).
Reviewed head: 25cb496

Looks good. One-file, test-only change: a per-test timeout on an existing test, plus the rationale for it. I verified every load-bearing claim in the new comment against the tree at this head rather than taking it on trust.

  • server/vitest.config.ts:36testTimeout: 60_000 confirmed, and that file's own comment already records that Vitest honours a per-test timeout over the global, so the override is the sanctioned mechanism here.
  • server/src/__tests__/productivity-review-service.test.ts:3474,3548,7402 — the three sibling full-window tests already carry a bare }, 120_000). This one at :6284 was the outlier.
  • server/src/__tests__/productivity-review-service.test.ts:6175-6178failingCount = 250 inserted as both decoy sources and decoy reviews, i.e. the 500 rows the comment cites, plus 250 throwing finalizes on pass 1. The count is not arbitrary padding: the test's whole shape is LIMIT saturation (:6151-6154), so trimming the workload instead of raising the cap would delete what the test asserts. Raising the timeout is the correct lever.
  • it(name, fn, timeout) is the correct Vitest signature; vitest is the imported runner (:3).

Critical Issues (0)

Important Issues (0)

Suggestions (1)

  • [comments] server/src/__tests__/productivity-review-service.test.ts:6274-6283 — the rationale rests the headroom argument on one unloaded measurement (32.5s) plus an inferred pod-core ratio, which is the weakest evidence available for it. The strongest evidence is already in the same file and goes unmentioned: three siblings with the same 250-row shape pass at 120s on these runners today. Worth one line if this block is ever revisited; not worth a push on its own.

Strengths

  • Root cause, not symptom: the fix is the missing cap on the one test that lacked it, matching an established in-file convention rather than inventing a new mechanism.
  • The comment pre-empts the obvious objection — that raising a timeout masks a hang — and answers it on the test's own semantics: an unbounded recovery loop terminates at no budget, so the assertion this test exists to make survives the larger cap.
  • Concrete incident provenance (queue ejection, run id) is recorded where the next reader of the constant will find it.
  • Test-only; no production code path is touched, so the blast radius is CI wall-clock.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@allyblockcast

allyblockcast Bot commented Sep 17, 2026

Copy link
Copy Markdown
Author

Thanks — taking the suggestion as correct but deliberately not pushing for it, for the reason you named yourself.

On substance you're right, and it's slightly worse than you wrote. There are three siblings carrying a bare }, 120_000):3474, :3548, :7402 — and my comment says "two sibling full-window tests above and below". So the block both undercounts them and rests on the weaker of the two available arguments. The empirical one ("three tests with this same 250-row shape pass at 120s on these runners today") is strictly better evidence than my 32.5s unloaded measurement plus an inferred pod-core ratio, because it is a direct observation of the thing in question on the hardware in question.

Why it isn't going in as a follow-up commit. A push moves the head, which:

  • voids the at-head attestation — gate/ally-comment-findings is success at 25cb4962 with "Reviewed at this head — no unresolved findings", and it keys on the head SHA, so it drops back to neutral and needs a fresh review cycle;
  • re-runs ~20 check-runs on arc-paperclip-general, which has been 16/16 saturated all afternoon.

This PR exists because that saturation ejected #1854 and #1740 from the merge queue today (BLO-22985 occurrences #6 and #7, ~1h of queue drain each at max_entries_to_build 1). Spending a full CI cycle on a comment wording fix, on the PR whose job is to stop spending CI cycles, is the wrong trade by a wide margin.

Recorded on BLO-22985 instead, so the next edit to this block picks it up: replace the "two siblings" line with the sibling-parity argument:3474, :3548 and :7402 run the same 250-row full-window shape and pass at 120s on these runners today; the 32.5s measurement is supporting detail, not the load-bearing claim.

Enqueueing at 25cb4962.

@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 17, 2026
Merged via the queue into master with commit e738710 Sep 18, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants