hcengineering/platform · warning · ApiError
err.code
err.code
Error message
err.message (500 fallback sends err object when message is empty)
What it means
The ai-bot server's global Express error handler logs the error, returns ApiError instances with their own code and message, and for all other errors responds 500 with {message: err.message} — or the raw err object when message is empty. Seeing the raw error object in the response means a non-ApiError with an empty/absent message escaped a handler.
Source
Thrown at services/ai-bot/pod-ai-bot/src/server/server.ts:192
const resp = await controller.getLoveIdentity(roomName)
if (resp === undefined) {
throw new ApiError(404)
}
res.status(200)
res.json(resp)
})
)
app.use((err: any, _req: any, res: any, _next: any) => {
console.log(err)
if (err instanceof ApiError) {
res.status(err.code).send({ code: err.code, message: err.message })
return
}
res.status(500).send(err.message?.length > 0 ? { message: err.message } : err)
})
return app
}
export function listen (e: Express, port: number, host?: string): Server {
const cb = (): void => {
console.log(`AI 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
- Read the raw error object in the 500 response (or console.log output) to identify the true failure.
- Wrap known failure paths in ApiError with a code and message so clients get structured errors.
- Ensure thrown values are Error instances with descriptive messages.
- Add middleware-level logging/serialization for non-Error throws.
Example fix
// before
throw { status: 502 }
// after
throw new ApiError(502, 'upstream service unavailable') Defensive patterns
Strategy: try-catch
Validate before calling
// Client: detect the raw-object 500 shape before using it
function extractServerMessage(body: unknown): string {
if (typeof body === 'object' && body !== null && 'message' in body && typeof (body as any).message === 'string') return (body as any).message
return 'unstructured server error: ' + JSON.stringify(body)
} Type guard
function isApiErrorPayload(x: unknown): x is { code: number; message: string } {
return typeof x === 'object' && x !== null && typeof (x as any).code === 'number' && typeof (x as any).message === 'string'
} Try / catch
try {
return await callAiBot(req)
} catch (err) {
if (err instanceof ApiError) {
return res.status(err.code).json({ code: err.code, message: err.message })
}
logger.error({ err }, 'unhandled ai-bot error')
return res.status(500).json({ message: err instanceof Error && err.message ? err.message : 'internal error' })
} Prevention
- Throw ApiError (with code + message) for every anticipated failure path.
- Never throw plain objects or strings; always throw Error subclasses.
- Await all promises in async routes so rejections carry proper messages.
- Add a final error middleware that serializes unknown throws into structured errors.
When it happens
Trigger: Any unhandled exception thrown in a route that is not an ApiError and whose err.message is empty or undefined — e.g. Error(''), non-Error objects thrown, or fetch/network errors without messages.
Common situations: Upstream HTTP client rejections yielding message-less errors; throwing plain objects or strings; bugs producing empty Error instances; unhandled promise rejections reaching the error middleware.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/42874b6fa824fe41.
Report an issue: GitHub.