{"record":{"id":"35b22ad03cefc603","repo":"HeyPuter/puter","slug":"bad-request-35b22a","errorCode":"bad_request","errorMessage":"`uid` must be a non-empty string","messagePattern":"`uid` must be a non-empty string","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/controllers/notification/NotificationController.ts","lineNumber":56,"sourceCode":"     * timestamp; pushes ack event to sockets.\n     */\n    @Post('/mark-ack', {\n        subdomain: 'api',\n        requireUserActor: true,\n        allowFullAccessToken: true,\n        // Fires per notification interaction, so the ceiling stays\n        // generous — it is here to catch a loop, not to pace a user.\n        rateLimit: {\n            scope: 'notification-mark',\n            limit: 300,\n            window: 60_000,\n            key: 'user',\n        },\n    })\n    async markAck(req: Request, res: Response): Promise<void> {\n        const uid = req.body?.uid;\n        if (typeof uid !== 'string' || uid.length === 0) {\n            throw new HttpError(400, '`uid` must be a non-empty string', {\n                legacyCode: 'bad_request',\n            });\n        }\n        const userId = req.actor?.user?.id;\n        if (!userId)\n            throw new HttpError(401, 'Unauthorized', {\n                legacyCode: 'unauthorized',\n            });\n\n        const notifService = this.services.notification as unknown as\n            NotificationService | undefined;\n        if (notifService?.markAcknowledged) {\n            await notifService.markAcknowledged(uid, userId);\n        } else {\n            // Fallback: direct store call if service isn't wired\n            await (\n                this.stores as Record<string, unknown> as {\n                    notification: {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/notification/NotificationController.ts#L38-L74","documentation":"`POST /notif/mark-ack` requires a `uid` field in the JSON body identifying the notification to acknowledge. The controller validates it is a non-empty string before doing any work. This is the first guard in the handler — it fires before the auth check.","triggerScenarios":"Calling mark-ack with a body missing `uid`, with `uid` set to a number/empty string/null, or without a JSON Content-Type so the body is unparsed. Common in frontend notification dismiss handlers that forget to pass the notification ID.","commonSituations":"A notification toast dismiss handler passes the DOM element ID instead of the notification UID; the notification object was null; a test posts `{}` as the body.","solutions":["Ensure the request body includes `uid` as a non-empty string matching the notification's UID.","Set `Content-Type: application/json` on the request.","Pull the UID from the notification object returned by the list-notifications endpoint.","Add a client-side guard: skip the call if `uid` is falsy."],"exampleFix":"// before\nnotifEl.ondismiss = () => api.call('notif/mark-ack', {});\n\n// after\nnotifEl.ondismiss = () => {\n  if (!notif.uid) return;\n  api.call('notif/mark-ack', { uid: notif.uid });\n};","handlingStrategy":"validation","validationCode":"function markAck(uid) {\n  if (typeof uid !== 'string' || uid.length === 0) return;\n  return fetch('/api/notif/mark-ack', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ uid }),\n  });\n}","typeGuard":"/** @param {unknown} v @returns {v is string} */\nfunction isNonEmptyString(v) {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Guard the dismiss handler: skip the API call when `uid` is missing.","Use the `uid` field name from the notification object, not `id`.","Set `Content-Type: application/json` on every POST."],"tags":["validation","notifications","api-input","http-400","bad-request"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}