Both reviewers' blocker. `githubAppCommand` checked the two ids for truthiness
only, then handed them to `Number()`. `--app-id nope` becomes NaN, and
`JSON.stringify(NaN)` is `null` — so on a path that deliberately persists
BEFORE calling Coolify, a typo wrote a credential record with a null app_id
and could upload the security key before `POST /github-apps` rejected it. A
half-run leaving a corrupt record on disk and a stray key on the server.
Validated with the other ARGV checks, ABOVE openCoolify/assertTeam rather than
in the register branch where I first put it. The first placement still let
`GET /teams/current` go out before the refusal — the new test caught that,
which is the argument for asserting "no stub hits" rather than "no writes". A
typo should cost nothing, not one request.
Digits-only rather than Number.isInteger: `1e3` and `0x10` are integers to
JavaScript but are not how a GitHub App id is written, and quietly storing 1000
for `1e3` is the same class of wrong answer as storing null for `nope`.
Coverage asserts both halves the review asked for — no stub hit AND an
unchanged state directory — across non-numeric (both flags), zero, decimal,
exponent and hex.
A negative id gets its own case rather than joining the loop: parseArgs reads
the leading dash as an option and rejects `-5` as unknown, exiting 1 rather
than 2. The property that matters still holds — refused before any write or
request — but it is a different path with a different exit code, and a
loosened shared assertion would have hidden that rather than recorded it.
Verified by mutation: disabling the check fails all six loop cases.