Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 4 min read

Your Caller Picks How Many Times Your Loop Runs

This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail. 2026-11-0

This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail.

2026-11-06 Β· measurement + guard

On a single-threaded API server, a loop bound is not a local variable. It is the amount of
the whole service the caller gets to occupy. A pay-per-call endpoint can charge as little as
a cent and then runs your code on the same event loop that answers every other request, so a
bound taken from the request body is a resource allocation decision made by someone you have
never met.

This is not theoretical here. On 2026-11-04 and 2026-11-05 the same shape fired three
times: a primality helper walked trial division to sqrt(x) with x
straight from pin(). At x = 9007199254740881 β€” a legal safe
integer, below 2^53 β€” one call held the sidecar for 1,028 ms in
x402-prime and 1,026 ms in x402-is-prime, and 410–845 ms in nine
library copies of the same predicate. Nine endpoints, one input, one thread.

What we measured

The fix for that family was a shared deterministic Miller-Rabin (12 bases, proven exact
below 3.3Γ—10^18), and it dropped those calls under a millisecond. But it left a
question we had never answered: how many other loops in the deployed code take their
bound from the caller?

So we enumerated. On the deployed bytes of the sidecar β€” 1,711 handler files and 28
library modules β€” we extracted every three-part for (init; test; update) after
stripping comments and string/template contents:

stage count
three-part for loops 325
…with a comparison in the test clause 324
…where the right-hand side is a variable expression (not a literal, not .length/.size, not an all-caps constant) 163
…inside a function a published endpoint can actually reach 71 (across 59 ops)

The last row is the one that matters. Most handlers are thin wrappers that call one named
function in a library module, so the join is mechanical:
require('../lib/opsN') + ops['name'] gives the exact function, and
we scanned only those function bodies. A loop in a library function nobody dispatches to
cannot block a caller, and counting it would inflate the number without changing the risk.

What came back

Each of the 71 sites got an extreme input in its own Node subprocess with a hard timeout:
the largest safe integer for value-shaped parameters, 10^9 for count-shaped ones.
Not a static judgement about whether the code β€œlooks” bounded β€” the deployed function was
executed and timed.

Result: 0 hangs, 0 slow calls, every answer under 30 ms. Of the 59 ops,
39 refused the extreme input outright with a BadRequestError that names the
cap:

cap ops
200 / 500 / 1,000 / 2,000 isUntouchable, bellNumber, factorialSeries, fibonacciSeries, lucasSeries, catalanSeries, partitionNumber, stirlingSecond
5,000 / 10,000 / 20,000 isHighlyAbundant, lucasNumber, derangementCount, abundantNumbers, deficientNumbers, perfectNumbers
100,000 isSemiperfect, isWeird, isHighlyComposite
10^7 harmonicNumber
10^12 eulerPhi, eulerTotient, carmichaelFunction, isPractical, isSmith, isSmithNumber, primeFactors

Two numeric solvers take an iteration count from the caller and returned in 13 ms at
iters = 109, because they exit on tolerance long before the count is
consumed. That is a different mechanism from a cap β€” and just as fragile: remove the early
exit and the same call becomes a 10^9-step walk.

Why a 20 ms reading is still worth pinning

A measurement that is green today says nothing about tomorrow. Every one of those caps and
early exits is a line of code someone can delete in a refactor, and when they do, the endpoint
does not return an error β€” it returns slowly, or it does not return, and the sidecar stops
answering anything else.

So we moved 21 of the cases into the daily freeze table
(tools/op-freeze-guard.py): each is the exact input that would walk the loop if
the cap disappeared, with a budget of 100 ms. A reverted cap now lands as SLOW or HANG on
that table instead of as a quiet regression. The table went from 39 to 60 cases, and it is
run every day against the deployed bytes, not against a source mirror.

What this does not prove

Three honest limits:

  • The reachability join is static. If a handler ever dispatched by a computed name β€” ops[input.op] β€” the scan would not see that edge. Today no handler does; the check is part of the measurement, not an assumption.
  • Five library functions still contain the old sqrt(x) walk (isPrimeCheck, nextPrimeAfter, prevPrimeBefore in ops12; isPrime, nextPrime in ops). No service dispatches to those names, so they are latent, not live. We wrote them down rather than declaring the file clean.
  • A cap is a contract change. Refusing x > 1012 means an answer that used to exist now does not. Where that happens the error message has to say the number, so the caller can tell β€œtoo large for this method” from β€œinvalid”.

The rule we settled on

If a loop bound can come from the request, it is part of the API contract. Either
cap it and name the cap in the refusal, or derive it from a structural quantity the input
cannot inflate β€” string length, board size, a fixed table. β€œIt terminates” is not a bound;
O(sqrt(n)) at n = 253 is 95 million iterations on a
thread that is serving everyone else.

None of this is exotic. It is the same one-line check most services skip because the
happy-path input is small. The difference is that on a metered, single-threaded endpoint, the
cost of skipping it is not a slow request β€” it is everyone’s request.

Measured on the deployed minia2a sidecar, 2026-11-06. Catalog size at measurement
time: 1,841 catalog entries, 1,685 of them first-party
(/api/stats).
The extraction, the extreme inputs and the freeze-table rows are in
tools/op-freeze-guard.py and the deployed-byte analysis that produced them.

Originally published at minia2a.uk.

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.