src/config/Constants.ts:27-31:
export const SecFetchMaxPerSec = ((): number => {
const raw = process.env.SEC_FETCH_MAX_PER_SEC?.trim();
const parsed = raw !== undefined && /^\d+$/.test(raw) ? Number(raw) : 4;
return parsed >= 1 && parsed <= 8 ? parsed : 8;
})();
Anything in range passes; anything out of range becomes the ceiling. For a value above 8 that
is a correct clamp down. For 0 it is an inversion: the one value an operator types meaning
less is the one value that produces more.
Measured
$ for v in "" 0 1 4 8 9 100 abc -1; do
SEC_FETCH_MAX_PER_SEC="$v" bun -e 'import {SecFetchMaxPerSec, SecFetchMaxConcurrent, SecSqliteCacheMb}
from "./src/config/Constants.ts";
console.log(`perSec=${SecFetchMaxPerSec} concurrent=${SecFetchMaxConcurrent} cacheMb=${SecSqliteCacheMb}`)'
done
SEC_FETCH_MAX_PER_SEC='' -> perSec=4 concurrent=4 cacheMb=256
SEC_FETCH_MAX_PER_SEC='0' -> perSec=8 concurrent=4 cacheMb=256 <-- 2× the default
SEC_FETCH_MAX_PER_SEC='1' -> perSec=1
SEC_FETCH_MAX_PER_SEC='4' -> perSec=4
SEC_FETCH_MAX_PER_SEC='8' -> perSec=8
SEC_FETCH_MAX_PER_SEC='9' -> perSec=8 (correct clamp down)
SEC_FETCH_MAX_PER_SEC='100' -> perSec=8 (correct clamp down)
SEC_FETCH_MAX_PER_SEC='abc' -> perSec=4 (falls back)
SEC_FETCH_MAX_PER_SEC='-1' -> perSec=4 (falls back)
Why it matters here specifically
The JSDoc four lines above the code says "Override DOWN via SEC_FETCH_MAX_PER_SEC (1–8); the
ceiling is clamped to 8 so we stay consistently under EDGAR's limit and a stray higher value can't
push us to the edge." A stray lower value pushes us to the edge instead.
And docs/fetch-and-storage.md records that EDGAR's rate-limit block is self-sustaining —
requests sent during a time-out extend it — which is why the default is held at 4 rather than at
EDGAR's documented 10. Doubling the rate is the failure mode the whole 451a0923 arc was built to
avoid.
The two neighbours already do it right
Same file, same shape, different outcome for 0:
// :62-66
return Math.min(64, Math.max(1, parsed || 4)); // SecFetchMaxConcurrent: 0 -> 4
// :96-100
return Math.min(4096, Math.max(2, parsed || 256)); // SecSqliteCacheMb: 0 -> 256
Fix
Either match the neighbours (Math.min(8, Math.max(1, parsed || 4))) or — better, and the policy
#323 has been asking for since 2026-08-24 — throw on a value outside 1–8, naming the variable
and the range, the way secEmbeddingDimensions() now throws on a malformed
SEC_EMBEDDING_DIMENSIONS (src/config/models.ts:73-80). That throw landed this window and is
the right precedent; this is the knob where silence costs the most.
Filed separately from #323 because it is not a swallowed typo — the value is read, accepted, and
inverted — and because it is the one knob whose failure mode is an IP block rather than a
suboptimal default.
Found during the 2026-09-14 review. Snapshot: workglow-dev/prd → analysis/grades/2026-09-14/sec-detailed.md §4.4.
src/config/Constants.ts:27-31:Anything in range passes; anything out of range becomes the ceiling. For a value above 8 that
is a correct clamp down. For
0it is an inversion: the one value an operator types meaningless is the one value that produces more.
Measured
Why it matters here specifically
The JSDoc four lines above the code says "Override DOWN via SEC_FETCH_MAX_PER_SEC (1–8); the
ceiling is clamped to 8 so we stay consistently under EDGAR's limit and a stray higher value can't
push us to the edge." A stray lower value pushes us to the edge instead.
And
docs/fetch-and-storage.mdrecords that EDGAR's rate-limit block is self-sustaining —requests sent during a time-out extend it — which is why the default is held at 4 rather than at
EDGAR's documented 10. Doubling the rate is the failure mode the whole
451a0923arc was built toavoid.
The two neighbours already do it right
Same file, same shape, different outcome for
0:Fix
Either match the neighbours (
Math.min(8, Math.max(1, parsed || 4))) or — better, and the policy#323 has been asking for since 2026-08-24 — throw on a value outside 1–8, naming the variable
and the range, the way
secEmbeddingDimensions()now throws on a malformedSEC_EMBEDDING_DIMENSIONS(src/config/models.ts:73-80). That throw landed this window and isthe right precedent; this is the knob where silence costs the most.
Filed separately from #323 because it is not a swallowed typo — the value is read, accepted, and
inverted — and because it is the one knob whose failure mode is an IP block rather than a
suboptimal default.
Found during the 2026-09-14 review. Snapshot:
workglow-dev/prd→analysis/grades/2026-09-14/sec-detailed.md§4.4.