{"record":{"id":"8e441c78fce706dd","repo":"thedotmack/claude-mem","slug":"message","errorCode":null,"errorMessage":"${message}","messagePattern":"\\$\\{message\\}","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"src/services/worker/http/BaseRouteHandler.ts","lineNumber":77,"sourceCode":"      ?? req.get?.('x-claude-mem-platform-source');\n    return BaseRouteHandler.firstString(req.query.platformSource)\n      ?? BaseRouteHandler.firstString(req.query.platform_source)\n      ?? BaseRouteHandler.firstString(body.platformSource)\n      ?? BaseRouteHandler.firstString(body.platform_source)\n      ?? BaseRouteHandler.firstString(header);\n  }\n\n  protected getPlatformSourceFromRequest(req: Request): string {\n    return normalizePlatformSource(BaseRouteHandler.rawPlatformSourceFromRequest(req));\n  }\n\n  protected getOptionalPlatformSourceFromRequest(req: Request): string | undefined {\n    const rawPlatformSource = BaseRouteHandler.rawPlatformSourceFromRequest(req);\n    return rawPlatformSource ? normalizePlatformSource(rawPlatformSource) : undefined;\n  }\n\n  protected badRequest(res: Response, message: string): void {\n    res.status(400).json({ error: message });\n  }\n\n  protected notFound(res: Response, message: string): void {\n    res.status(404).json({ error: message });\n  }\n\n  protected handleError(res: Response, error: Error, context?: string): void {\n    const statusCode = error instanceof AppError ? error.statusCode : 500;\n    // Client errors (4xx AppErrors) are routine bad input, not server faults, so\n    // they log at WARN and are NOT routed to the error sink — surfacing a\n    // validation rejection like a bad corpus name as a captured $exception just\n    // pollutes error tracking with noise. Only true server faults (5xx, or any\n    // non-AppError, which maps to 500) go through logger.failure, whose Error\n    // payload routes through logger.error → the error sink → captureException\n    // (Phase 3): a REDACTED $exception to PostHog Error Tracking, consent-gated,\n    // kill-switch-gated, and rate-limited.\n    const isClientError = statusCode >= 400 && statusCode < 500;\n    if (isClientError) {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/e2d1df569a8f04075d40e92461128ece7cf04c82/src/services/worker/http/BaseRouteHandler.ts#L59-L95","documentation":"Not a thrown exception but the worker's standard 400 Bad Request envelope, emitted by BaseRouteHandler.badRequest(res, message). Any route handler that validates input manually calls this helper instead of throwing; the response body is { error: <message> } with no issues array (structured Zod rejections use validateBody instead).","triggerScenarios":"Calling a worker API with malformed parameters that the handler checks imperatively: bad session id format, missing query text, invalid pagination, unknown platform source, etc. The exact message comes from the specific route handler.","commonSituations":"Clients passing undefined/null fields due to destructuring mistakes, wrong units (string vs number), or assuming an older API shape after a worker upgrade changed validation rules.","solutions":["Read the error string in the 400 body — it states exactly which parameter failed","Compare your payload against the route's Zod schema or handler destructuring in the matching *Routes.ts file","Fix the field name/type in the request and resend"],"exampleFix":"// before\nawait fetch(`${base}/api/sessions/abc/complete`, { method: 'POST' }); // 400 'invalid session id'\n\n// after\nconst id = Number(rawId);\nif (!Number.isInteger(id)) throw new TypeError('session id must be an integer');\nawait fetch(`${base}/api/sessions/${id}/complete`, { method: 'POST' });","handlingStrategy":"validation","validationCode":"function assertSessionId(id: unknown): asserts id is number {\n  if (!Number.isInteger(id) || (id as number) <= 0)\n    throw new RangeError(`bad session id: ${String(id)}`);\n}","typeGuard":"function isBadRequest(body: unknown): body is { error: string } {\n  return typeof body === 'object' && body !== null &&\n    typeof (body as { error?: unknown }).error === 'string';\n}","tryCatchPattern":"const res = await callApi();\nif (res.status === 400) {\n  const { error } = await res.json();\n  throw new InputError(error); // surface the exact field message to the caller/UI\n}","preventionTips":["Validate request shapes client-side before sending; mirror the worker's schemas","Log the 400 body verbatim — the message names the exact offending parameter","Pin client and worker versions together so validation rules stay in sync"],"tags":["http-400","bad-request","input-validation","express"],"backgroundTag":"http-400-bad-request","analyzedSha":"e2d1df569a8f04075d40e92461128ece7cf04c82","analyzedAt":"2026-08-20T23:58:13.836Z","contentChangedAt":"2026-08-20T23:58:13.836Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}