hcengineering/platform · critical

err.message

Error message

err.message

What it means

The pod-notification error middleware's last branch responds HTTP 500 { message: err.message } for any thrown error that is not an ApiError. This is an unexpected server-side exception in a notification endpoint handler (web-push send failure, TypeError, network error) that the framework surfaces as a 500. The client sees only the raw message; the stack lives in the pod's logs.

Source

Thrown at services/notification/pod-notification/src/server.ts:56

  endpoints.forEach((endpoint) => {
    if (endpoint.type === 'get') {
      app.get(endpoint.endpoint, catchError(endpoint.handler))
    } else if (endpoint.type === 'post') {
      app.post(endpoint.endpoint, catchError(endpoint.handler))
    }
  })

  app.use((_req, res, _next) => {
    res.status(404).send({ message: 'Not found' })
  })

  app.use((err: any, _req: any, res: any, _next: any) => {
    if (err instanceof ApiError) {
      res.status(400).send({ code: err.code, message: err.message })
      return
    }

    res.status(500).send({ message: err.message })
  })

  return app
}

export function listen (e: Express, port: number, host?: string, onListening?: () => void): Server {
  const cb = (): void => {
    if (onListening !== undefined) {
      onListening()
    } else {
      console.log(`Notification service has been started at ${host ?? '*'}:${port}`)
    }
  }

  return host !== undefined ? e.listen(port, host, cb) : e.listen(port, cb)
}

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Check pod-notification's server logs for the full stack trace; the response body only contains err.message.
  2. Purge invalid/expired PushSubscriptions (upstream 404/410) from storage so sends don't throw.
  3. Verify VAPID key configuration (public/private keys present and valid) before calling the push endpoint.
  4. Harden handler code to catch per-subscription send failures individually and re-throw as ApiError for expected conditions.

Example fix

// before
await Promise.all(subs.map(s => webpush.sendNotification(s, payload)))
// after
for (const s of subs) {
  try {
    await webpush.sendNotification(s, payload)
  } catch (e) {
    if ((e as any).statusCode === 404 || (e as any).statusCode === 410) await removeSubscription(s)
    else throw new ApiError('PUSH_SEND_FAILED', (e as Error).message)
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.VAPID_PUBLIC_KEY || !process.env.VAPID_PRIVATE_KEY) throw new Error('VAPID keys must be configured before calling the push endpoint')

Try / catch

try {
  const res = await fetch(pushUrl, opts)
  if (res.status === 500) {
    const { message } = await res.json()
    throw new Error(`notification pod crashed: ${message} (see pod logs)`)
  }
} catch (e) { /* backoff/retry transient errors; alert on repeated failures */ }

Prevention

When it happens

Trigger: A handler throws a non-ApiError — e.g. web-push's sendNotification rejects for an invalid/expired subscription, a TypeError from malformed internal state, or an unhandled promise rejection inside the push loop — and catchError forwards it to the error middleware.

Common situations: Stale subscriptions whose endpoints now return 4xx/5xx upstream; VAPID keys misconfigured or missing causing web-push to throw; a single bad subscription aborting a batch send; memory/timeout pressure under large subscription lists.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/5c1cc72bd58af3bf. Report an issue: GitHub.