Problem
Only 5 of 203 API routes validate their body with a schema. The rest duck-type, for example app/api/repos/[id]/rules/route.ts:84 checks body.rule_id && typeof body.excluded === "boolean" and then writes body.rule_id straight into an upsert.
The routes with the highest blast radius also have no tests at all: everything under app/api/teams/[teamId]/ (RBAC: members, invites, keys, models, installations, icon) and app/api/connections/ (OAuth connect and callback).
Where
- Routes that already use zod, to copy from:
app/api/v1/mogplex/repos/[repoId]/env-vars, app/api/runs/[runId]/guidance, app/api/control/continuations.
- Untested targets by size:
app/api/teams/[teamId]/invites/bulk (224 lines), icon (223), invites (198), installations (171), invites/[inviteId] (163), members (158), keys (155), models (152), app/api/connections/route.ts (206), oauth (116), oauth/callback (111).
Fix
One PR per route. For each:
- Define a zod schema for the body next to the route (
schema.ts in the route directory).
- Parse with
safeParse; return 400 with the flattened error on failure.
- Add a
tests/unit/ suite covering success, invalid body, and the auth failure path (the testing policy requires all three for API routes).
Start with members and invites, since those decide who can see what.
Exit criteria
Out of scope
The other 80-odd routes without zod. Once the pattern is established, open one issue per area.
https://claude.ai/code/session_01SQ4nS96XYRztd5w9QkubPf
Problem
Only 5 of 203 API routes validate their body with a schema. The rest duck-type, for example
app/api/repos/[id]/rules/route.ts:84checksbody.rule_id && typeof body.excluded === "boolean"and then writesbody.rule_idstraight into an upsert.The routes with the highest blast radius also have no tests at all: everything under
app/api/teams/[teamId]/(RBAC: members, invites, keys, models, installations, icon) andapp/api/connections/(OAuth connect and callback).Where
app/api/v1/mogplex/repos/[repoId]/env-vars,app/api/runs/[runId]/guidance,app/api/control/continuations.app/api/teams/[teamId]/invites/bulk(224 lines),icon(223),invites(198),installations(171),invites/[inviteId](163),members(158),keys(155),models(152),app/api/connections/route.ts(206),oauth(116),oauth/callback(111).Fix
One PR per route. For each:
schema.tsin the route directory).safeParse; return 400 with the flattened error on failure.tests/unit/suite covering success, invalid body, and the auth failure path (the testing policy requires all three for API routes).Start with
membersandinvites, since those decide who can see what.Exit criteria
pnpm lint,pnpm typecheck,pnpm test:unitpass after each PR.Out of scope
The other 80-odd routes without zod. Once the pattern is established, open one issue per area.
https://claude.ai/code/session_01SQ4nS96XYRztd5w9QkubPf