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
- Check pod-notification's server logs for the full stack trace; the response body only contains err.message.
- Purge invalid/expired PushSubscriptions (upstream 404/410) from storage so sends don't throw.
- Verify VAPID key configuration (public/private keys present and valid) before calling the push endpoint.
- 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
- Verify VAPID key configuration before enabling push sends
- Purge subscriptions that upstream rejects with 404/410 so batch sends don't throw
- Apply per-subscription error isolation server-side to keep one bad entry from failing the batch
- Alert on 500 rates and keep pod logs retained for stack traces
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.