Number() is applied inside a zod .transform() when coercing config strings to numbers. A transform's output is not revalidated, so a non-numeric value does not fail validation — it succeeds as NaN. There is no error for the trailing .catch() to catch, and the next time the package writes the file, NaN goes back into it.
Sites in this package:
startos/fileModels/config.ts:29 — z.array(z.string()).transform((a) => Number(a.at(-1))),
startos/fileModels/config.ts:30 — z.string().transform(Number),
Fix
Add .pipe(z.number()) after the union, before .optional() / .catch():
const iniNumber = z
.union([
z.array(z.string()).transform((a) => Number(a.at(-1))),
z.string().transform(Number),
z.number(),
])
.pipe(z.number()) // revalidate the transform's output
.optional()
.catch(undefined)
Each field then falls back to whatever its .catch() already declares, instead of carrying NaN forward.
Why not an isNaN guard
z.number() rejects NaN and is finite by default, so the pipe also covers values an explicit isNaN check lets through:
| input |
as shipped |
explicit isNaN guard |
.pipe(z.number()) |
"abc" |
NaN |
falls back |
falls back |
"1e999" |
Infinity |
Infinity |
falls back |
"Infinity" |
Infinity |
Infinity |
falls back |
Severity
Only reachable by hand-editing the config file — form input is validated separately by Value.number, which rejects non-numbers correctly. The result is silent rather than loud, though: in bitcoind, maxconnections=NaN is read as 0, a node that makes and accepts no peer connections at all. Worth checking what this daemon does with NaN in its numeric keys.
Context
Same pattern and same fix in 13 packages across both registries. It spread by copy-paste between packages — the packaging guide never mentions it. Already fixed in bitcoind: Start9Labs/bitcoin-core-startos#279, #280, #281, #282 and Start9Labs/bitcoin-knots-startos#51, #52.
No urgency — pick it up next time this package is touched.
Number()is applied inside a zod.transform()when coercing config strings to numbers. A transform's output is not revalidated, so a non-numeric value does not fail validation — it succeeds asNaN. There is no error for the trailing.catch()to catch, and the next time the package writes the file,NaNgoes back into it.Sites in this package:
startos/fileModels/config.ts:29—z.array(z.string()).transform((a) => Number(a.at(-1))),startos/fileModels/config.ts:30—z.string().transform(Number),Fix
Add
.pipe(z.number())after the union, before.optional()/.catch():Each field then falls back to whatever its
.catch()already declares, instead of carryingNaNforward.Why not an
isNaNguardz.number()rejectsNaNand is finite by default, so the pipe also covers values an explicitisNaNcheck lets through:isNaNguard.pipe(z.number())"abc"NaN"1e999"InfinityInfinity"Infinity"InfinityInfinitySeverity
Only reachable by hand-editing the config file — form input is validated separately by
Value.number, which rejects non-numbers correctly. The result is silent rather than loud, though: in bitcoind,maxconnections=NaNis read as0, a node that makes and accepts no peer connections at all. Worth checking what this daemon does withNaNin its numeric keys.Context
Same pattern and same fix in 13 packages across both registries. It spread by copy-paste between packages — the packaging guide never mentions it. Already fixed in
bitcoind: Start9Labs/bitcoin-core-startos#279, #280, #281, #282 and Start9Labs/bitcoin-knots-startos#51, #52.No urgency — pick it up next time this package is touched.