From 94b25bcc3631e0d68eff0beca0765c8ac5e026ac Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 11 Sep 2026 23:55:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(PROJ-11):=20BUG-24=20=E2=80=94=20duplicate?= =?UTF-8?q?=20team-member=20add=20returned=20500,=20not=20409?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First attempt checked err.code, which is undefined — drizzle-orm wraps the raw pg error (the one that actually has .code = '23505' for a unique violation) in .cause. Live-tested and caught before assuming the fix worked from code review alone. Now checks err.cause?.code first, falls back to err.code. Verified: first add 201, duplicate add 409 "Member is already on this team" instead of 500. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0143yYvjesjHndBACoweyK67 --- backend/src/routes/teams.ts | 15 ++++++++++++++- features/PROJ-11-chat.md | 8 ++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/teams.ts b/backend/src/routes/teams.ts index f681776..e4281b3 100644 --- a/backend/src/routes/teams.ts +++ b/backend/src/routes/teams.ts @@ -152,7 +152,20 @@ export async function teamRoutes(app: FastifyInstance) { return reply.code(403).send({ error: "Forbidden" }); } - await db.insert(teamMembers).values({ teamId, memberId }); + try { + await db.insert(teamMembers).values({ teamId, memberId }); + } catch (err) { + // BUG-24: duplicate add used to fall through to the global error handler + // as a generic 500 — no longer leaking SQL (BUG-19 fix), but still the + // wrong status for "this member is already on this team". + // drizzle-orm wraps the raw pg error (which has the .code) in .cause. + const pgCode = (err as { code?: string; cause?: { code?: string } }).cause?.code + ?? (err as { code?: string }).code; + if (pgCode === "23505") { + return reply.code(409).send({ error: "Member is already on this team" }); + } + throw err; + } const [room] = await db .select({ matrixRoomId: chatRooms.matrixRoomId }) diff --git a/features/PROJ-11-chat.md b/features/PROJ-11-chat.md index 5c1b5b2..5b1da0a 100644 --- a/features/PROJ-11-chat.md +++ b/features/PROJ-11-chat.md @@ -934,3 +934,11 @@ Live verifiziert: Token liefert vor dem Logout `200` auf `/account/whoami`, nach ### Status **Production-ready: JA** — beide Auflagen aus Runde 4 sind erledigt und live verifiziert. Verbleibend nur noch bewusst akzeptierte/dokumentierte Punkte (BUG-11, BUG-2/20-Restrisiko) und der manuelle iOS-Safari-Tastatur-Test. + +## BUG-24-Fix (2026-09-11) + +**Behoben, live verifiziert — brauchte zwei Versuche.** Doppeltes Hinzufügen eines Mitglieds zum selben Team lieferte `500` statt `409`. Erster Fix-Versuch (`(err as { code }).code === "23505"`) griff nicht — Live-Test zeigte weiterhin `500`. Ursache: `drizzle-orm` wrappt den rohen `pg`-Fehler (der den `.code` trägt) in `.cause`, nicht direkt auf dem geworfenen `DrizzleQueryError`. Fix geprüft `err.cause?.code` zuerst, fällt auf `err.code` zurück. + +Live verifiziert: erstes Hinzufügen `201`, zweites (Duplikat) `409 {"error":"Member is already on this team"}` statt `500`. + +**Lehre, konsistent mit BUG-23:** ein Fix, der nur nach Code-Lesen plausibel aussieht, ist erst durch einen echten Request gegen die Fehlerbehandlung verifiziert — die Fehler-Wrapping-Tiefe von ORM-Bibliotheken lässt sich am Typsystem nicht ablesen (`err.code` kompiliert anstandslos, obwohl es zur Laufzeit `undefined` ist).