Repository navigation
fix(admin): clamp the recompute batch size to 1..500 - #169
zuey-cloud-harness[bot] wants to merge 1 commit into
Conversation
batch=0 passed validation, became LIMIT 0, and the all=true loop then re-read the same offset for all 50 permitted iterations while processing nothing. batch=100000 was accepted too, defeating the per-request guard that keeps the Worker under its CPU limit. parseBatchSize in lib/admin/batch-limit.ts rejects anything below 1 or non-finite, clamps above 500, floors fractions, and falls back to 100. It uses Number rather than parseInt so a value like "1e9" is read as a magnitude and clamped instead of being truncated to 1. Refs #28
|
Claimed and implemented by AKAdmin on 2026-10-31 (Asia/Saigon). Branch |
clark-cant
left a comment
There was a problem hiding this comment.
Review — PR #169 fix(admin): clamp the recompute batch size to 1..500
Summary: Replaces the unvalidated parseIntSafe call on ?batch= with a new parseBatchSize() helper (positive integers only, cap 500, fallback 100, floors fractions, Number-based so 1e9 clamps instead of truncating to 1), and adds a six-case unit test. parseIntSafe stays for offset only, with a comment saying so.
Risk level: Low.
Mandatory gates
- Duplicate / prior implementation: clear —
gh pr list --search "batch clamp recompute"returns only this PR; nothing merged or open implements the clamp. - Project standards: docs found (
CLAUDE.md) — 45 and 51 new lines are well under the 200-LOC limit;apps/web/app/lib/admin/is a domain anchor, not a dumping-ground dir; the test path is picked up by the rootvitest.config.tsincludeapps/web/app/**/*.test.ts; the~alias resolves in both tsconfig and vitest. - Strategic necessity: clear value — closes the
batch=0spin in #28 and adds the upper bound the endpoint never had. Side benefit for #29:./scripts/run-recompute.sh 0forwards its argument unvalidated, so the server-side clamp is now the thing that makes that call safe.
Verified at d2e7c44
- Behaviour reproduced independently: re-ran
parseBatchSizethrough Node type stripping over 24 inputs —null/undefined/""/" "/"abc"/"NaN"/"Infinity"/"-0"/"0"/"0.0"/"-25"→ 100,"1"→ 1," 250 "→ 250,"500"→ 500,"12.9"→ 12,"501"/"100000"/"1e9"→ 500, and the caller-supplied{max: 50, fallback: 10}bound works. All match the tests in the PR, so the PR-body claim of 21 inputs is credible. - Single call site:
grep -rn recomputeBatch apps/webreturns only this route plus the definition, so no other path can handbatchSizestraight to.limit(). isComplete = batch.length < batchSize(recompute-all-skills.ts:177) is now safe from the route becausebatchSize >= 1is guaranteed, so an empty batch completes instead of re-reading the same offset.- CI
web-contracts-cliCOMPLETED SUCCESS at 2026-10-31T12:11:33Z; MERGEABLE / CLEAN.
Findings
Critical: none.
Important: none.
Suggestion — batch=0 becomes 100, which is neither option in #28. The issue asked "clamp to 1, or return 400"; the PR picks a third answer: treat 0 as unusable and fall back. That is defensible (0 is a missing/garbage value, not a request for one row), but it is currently implicit — the endpoint header (api.admin.recompute.ts:1-13) still advertises "custom batch size" with no bounds, so a caller sending batch=0 sees 100 processed and no signal. One header line documenting the accepted range would close it; a sentence in #28 recording the decision would stop the next reader re-litigating it (#29's docs gap is the same family).
Suggestion — the cap bounds one batch, not one request. With all=true the route still runs up to MAX_BATCHES_PER_REQUEST = 50, and recomputeBatch issues one db.update per skill, so 500 × 50 is up to ~25,000 D1 round-trips in a single request against the Workers subrequest budget — which is the resource MAX_BATCHES_PER_REQUEST exists to protect. Not a regression (the value was previously unbounded), and real catalogs rarely fill a batch, but if the clamp is meant to be the CPU guard, the product is the number that matters: either align max with the 100 default, or add a per-request row budget.
Suggestion — make the invariant local to the library. recomputeBatch is exported and its completion test is still batch.length < batchSize, so any future non-HTTP caller passing 0 re-creates the #28 loop. if (batchSize < 1) batchSize = 1 (or treating an empty batch as complete) at the top of recomputeBatch would put the guard where the loop lives instead of relying on the route being the only caller.
Verdict: Comment — minor suggestions only, safe to merge as-is. Not merged here: reviewDecision is empty and the akadmin-wip label says the AKAdmin flow owns this branch, so the merge stays with the owner/that flow. Once approved it is a one-click merge — CLEAN state, green web-contracts-cli, closes #28.
Posted by github-maintain (ck:review-pr profile) at 2026-10-31T19:05Z
Closes #28.
batchonPOST /api/admin/recomputewent straight throughparseIntSafe, which only rejectedNaNand negatives. Two things followed:batch=0passed, becameLIMIT 0, and an empty batch madeisComplete = batch.length < batchSizeevaluate0 < 0= false. Withall=truethe loop then re-read the same offset for all 50 permitted iterations and answered "Paused after 50 batches. Call again with resume=true" while having processed nothing.batch=100000was accepted, which defeats the per-request guard that keeps the Worker under its CPU limit.This adds
apps/web/app/lib/admin/batch-limit.tswithparseBatchSize(raw, limits): anything absent, blank, non-finite or below 1 falls back to 100, anything above 500 is clamped to 500, and fractions are floored because.limit()needs an integer. It usesNumberrather thanparseInt, so"1e9"is read as a magnitude and clamped instead of being truncated to1.The route now calls
parseBatchSize;parseIntSafestays foroffsetonly, and its doc comment says so.Tests live in
apps/web/app/lib/admin/batch-limit.test.ts(six cases covering fallback, zero and negatives, in-range values, fractions, the cap, and a caller-supplied bound). The parser itself was also checked against 21 inputs with Node's type stripping; all passed.No behaviour change for any caller already sending a sensible
batch.