fix(swagger): hoist the one inline predicate that makes the generator run away #164

Merged
whitlocktech merged 1 commits from fix/swagger-generator-runaway into edge 2026-08-29 02:09:29 +00:00
Member

What & why

npm run swagger cannot run on this tree at all. It dies with swagger-autogen's

FATAL ERROR: invalid array length - Allocation failed - JavaScript heap out of memory

and generates nothing. So the committed OpenAPI spec cannot be regenerated by anyone, and the standing obligation in CLAUDE.md — "never ship a route that isn't in the spec" — is currently unmeetable. This blocks engagement Phase 1, which removes two routes, and every later phase that adds one.

It is not a local quirk: it reproduces on a pristine origin/edge worktree, under Node 24 and Node 20 (the CI version, checked in a container), and at 61abb3e — the commit whose own PR last regenerated the spec.

The cause

Bisected to a single statement in server/src/router/v1/admin/teams.router.js:

param('teamId').custom((v) => v === 'default' || TEAM_ID.test(v))

Hoisting that arrow to a named const fixes it outright.

The diagnosis this corrects

Three files currently tell the next person the wrong thing, so the comments are corrected too.

Phase 8 shipped a bare regex literal before .test(. Phase 9 hit the crash, hoisted the regex, and read the symptom as a per-file route limit — measured at 20 teamsRouter.* statements — then split teamsVoice.router.js out and left teams.router.js at 19, "one under the edge", with that reasoning written into teams.router.js, teamsVoice.router.js and admin/index.js.

There is no edge to sit under. Probing every router file individually:

Probe Result
teams.router.js, 19 statements runaway
the same file truncated to 18 generates
a 3-route file carrying only the DELETE /integrations/:teamId route runaway
the same 3-route file with the .custom(...) arrow hoisted generates
every other router file in the tree generates

So the trigger is the inline arrow reaching .test(, not how much is in the file. The count was a proxy for how much text the parser chewed before reaching the real trigger, which is why removing any statement appeared to help. Hoisting the regex was half the fix; the predicate wrapped around it needed hoisting too.

The rule the comments now state: nothing that reaches .test( may sit inside a route statement.

The teamsVoice.router.js split is left in place — it is a good split on its own terms — but its note now says so rather than claiming it is mechanically load-bearing.

How it was tested

  • npm run swaggersucceeds, first time on this tree. The regenerated swagger-output.json is byte-identical to the committed one, so this is a generator fix and not a spec change; nothing is committed for it.
  • npm run routes:manifest -- --check — clean, no URL moved.
  • npm test --prefix server1162 passing, 0 failing.
  • npm run check:modules — clean.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally.
  • I have added or updated tests/docs where it makes sense.
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a Co-Authored-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later), and I have the right to contribute it.
## What & why **`npm run swagger` cannot run on this tree at all.** It dies with swagger-autogen's ``` FATAL ERROR: invalid array length - Allocation failed - JavaScript heap out of memory ``` and generates nothing. So the committed OpenAPI spec cannot be regenerated by anyone, and the standing obligation in CLAUDE.md — "never ship a route that isn't in the spec" — is currently unmeetable. This blocks engagement Phase 1, which removes two routes, and every later phase that adds one. It is not a local quirk: it reproduces on a **pristine `origin/edge` worktree**, under **Node 24 and Node 20** (the CI version, checked in a container), and at **`61abb3e`** — the commit whose own PR last regenerated the spec. ### The cause Bisected to a single statement in `server/src/router/v1/admin/teams.router.js`: ```js param('teamId').custom((v) => v === 'default' || TEAM_ID.test(v)) ``` Hoisting that arrow to a named const fixes it outright. ### The diagnosis this corrects Three files currently tell the next person the wrong thing, so the comments are corrected too. Phase 8 shipped a bare regex **literal** before `.test(`. Phase 9 hit the crash, hoisted the regex, and read the symptom as a **per-file route limit** — measured at 20 `teamsRouter.*` statements — then split `teamsVoice.router.js` out and left `teams.router.js` at 19, "one under the edge", with that reasoning written into `teams.router.js`, `teamsVoice.router.js` and `admin/index.js`. There is no edge to sit under. Probing every router file individually: | Probe | Result | | --- | --- | | `teams.router.js`, 19 statements | **runaway** | | the same file truncated to 18 | generates | | a **3-route** file carrying only the `DELETE /integrations/:teamId` route | **runaway** | | the same 3-route file with the `.custom(...)` arrow hoisted | generates | | every other router file in the tree | generates | So the trigger is the inline arrow reaching `.test(`, not how much is in the file. The count was a proxy for how much text the parser chewed before reaching the real trigger, which is why removing *any* statement appeared to help. Hoisting the regex was half the fix; the predicate wrapped around it needed hoisting too. The rule the comments now state: **nothing that reaches `.test(` may sit inside a route statement.** The `teamsVoice.router.js` split is left in place — it is a good split on its own terms — but its note now says so rather than claiming it is mechanically load-bearing. ## How it was tested - `npm run swagger` — **succeeds**, first time on this tree. The regenerated `swagger-output.json` is **byte-identical** to the committed one, so this is a generator fix and not a spec change; nothing is committed for it. - `npm run routes:manifest -- --check` — clean, no URL moved. - `npm test --prefix server` — **1162 passing, 0 failing.** - `npm run check:modules` — clean. ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. - [x] I have added or updated tests/docs where it makes sense. - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code`. I have reviewed and understand every change, and take responsibility for it. AI-authored commits are marked with a `Co-Authored-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**), and I have the right to contribute it.
wtclaude added 1 commit 2026-08-29 01:52:48 +00:00
fix(swagger): hoist the one inline predicate that makes the generator run away
All checks were successful
PR Checks / bot-tests (pull_request) Successful in 26s
PR Checks / client-build (pull_request) Successful in 26s
PR Checks / server-tests (pull_request) Successful in 10m42s
e25e7ade80
`npm run swagger` cannot run on this tree. It dies with swagger-autogen's
"FATAL ERROR: invalid array length - Allocation failed", generating
nothing, and it reproduces on a pristine checkout under both Node 20 and
Node 24 — so the committed spec cannot be regenerated by anyone, and any
PR that adds or changes a route is unable to meet the standing obligation
to update it.

Bisected to one statement in `teams.router.js`:

  param('teamId').custom((v) => v === 'default' || TEAM_ID.test(v))

Hoisting that arrow to a named const fixes it outright. Nothing else
changes and the regenerated spec is byte-identical to the committed one,
so this is a generator fix, not a spec change.

The diagnosis worth keeping, because the file's own comment recorded a
different one. Phase 8 shipped a bare regex LITERAL before `.test(` and
phase 9 hoisted the regex, blaming a per-file route limit measured at
twenty statements; the file has sat at nineteen ever since on the theory
that it was one under the edge. That theory is wrong. Probing every
router file individually, `teams.router.js` at nineteen statements dies
while a THREE-route file carrying only this one route also dies — so the
trigger is the inline arrow reaching `.test(`, not the count. Hoisting
the regex was half the fix; the predicate around it needed hoisting too.

The comments in `teams.router.js`, `teamsVoice.router.js` and
`admin/index.js` are corrected to say so, since all three currently tell
the next person to keep counting statements.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit b77e817fb1 into edge 2026-08-29 02:09:29 +00:00
whitlocktech deleted branch fix/swagger-generator-runaway 2026-08-29 02:09:30 +00:00
Sign in to join this conversation.
No description provided.