HeyPuter/puter · error · HttpError

bad_request

bad_request

Error message

`uid` must be a non-empty string

What it means

`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.

Source

Thrown at src/backend/controllers/notification/NotificationController.ts:56

     * timestamp; pushes ack event to sockets.
     */
    @Post('/mark-ack', {
        subdomain: 'api',
        requireUserActor: true,
        allowFullAccessToken: true,
        // Fires per notification interaction, so the ceiling stays
        // generous — it is here to catch a loop, not to pace a user.
        rateLimit: {
            scope: 'notification-mark',
            limit: 300,
            window: 60_000,
            key: 'user',
        },
    })
    async markAck(req: Request, res: Response): Promise<void> {
        const uid = req.body?.uid;
        if (typeof uid !== 'string' || uid.length === 0) {
            throw new HttpError(400, '`uid` must be a non-empty string', {
                legacyCode: 'bad_request',
            });
        }
        const userId = req.actor?.user?.id;
        if (!userId)
            throw new HttpError(401, 'Unauthorized', {
                legacyCode: 'unauthorized',
            });

        const notifService = this.services.notification as unknown as
            NotificationService | undefined;
        if (notifService?.markAcknowledged) {
            await notifService.markAcknowledged(uid, userId);
        } else {
            // Fallback: direct store call if service isn't wired
            await (
                this.stores as Record<string, unknown> as {
                    notification: {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Ensure the request body includes `uid` as a non-empty string matching the notification's UID.
  2. Set `Content-Type: application/json` on the request.
  3. Pull the UID from the notification object returned by the list-notifications endpoint.
  4. Add a client-side guard: skip the call if `uid` is falsy.

Example fix

// before
notifEl.ondismiss = () => api.call('notif/mark-ack', {});

// after
notifEl.ondismiss = () => {
  if (!notif.uid) return;
  api.call('notif/mark-ack', { uid: notif.uid });
};
Defensive patterns

Strategy: validation

Validate before calling

function markAck(uid) {
  if (typeof uid !== 'string' || uid.length === 0) return;
  return fetch('/api/notif/mark-ack', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ uid }),
  });
}

Type guard

/** @param {unknown} v @returns {v is string} */
function isNonEmptyString(v) {
  return typeof v === 'string' && v.length > 0;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/35b22ad03cefc603. Report an issue: GitHub.