Logging Standard (NON-NEGOTIABLE)
No silent failures. Every log.error() MUST include stack. ESLint rule cmmd/require-error-logging-stack enforces. Never console.error in server code.
No silent failures. Every
log.error()MUST includestack:. The 2026-03-25 S3 SignatureDoesNotMatch bug took hours to diagnose because logs didn’t have stack traces.
The two rules
Section titled “The two rules”1. Never use console.error
Section titled “1. Never use console.error”Use log from server/lib/logger. Enforced by ESLint rule.
2. Every log.error includes stack:
Section titled “2. Every log.error includes stack:”ESLint rule cmmd/require-error-logging-stack enforces this.
log.error('[PREFIX] What failed', { error: getErrorMessage(e), stack: e instanceof Error ? e.stack : undefined, organizationId, userId, ...context,});Without stack:, Railway logs are useless.
Anatomy of a good error log
Section titled “Anatomy of a good error log”| Field | Purpose |
|---|---|
Message prefix [PREFIX] | Context tag (e.g. [SIDEKICK-CHAT], [BRAIN-SEARCH], [COMPOSIO]) |
| What failed | Short imperative — “Failed to fetch user”, “Migration query timed out” |
error | Normalized error message via getErrorMessage(e) |
stack | Full stack trace — required |
organizationId | For org-scoped operations |
userId | If user-attributed |
...context | Anything else relevant — request ID, params, durations |
getErrorMessage(e) for unknown errors
Section titled “getErrorMessage(e) for unknown errors”Errors in catch blocks are unknown (TypeScript strict). Normalize:
import { getErrorMessage } from 'server/lib/error-utils';
try { // ...} catch (error: unknown) { log.error('[ROUTE-XYZ] Operation failed', { error: getErrorMessage(error), stack: error instanceof Error ? error.stack : undefined, organizationId: req.organizationId, }); throw error;}Log levels
Section titled “Log levels”| Level | When |
|---|---|
log.debug | Verbose, dev-only context |
log.info | Notable events (“Migration applied”, “Sidekick session started”) |
log.warn | Recoverable issues (“Retry triggered”, “Cache miss”) |
log.error | Failures — always with stack |
log.fatal | Unrecoverable — process exit imminent |
Default to log.info for hot paths. Don’t paper the logs with log.debug that gets disabled in prod.
Frontend errors
Section titled “Frontend errors”- 🚫 Don’t
console.errorin client code either (eventually) - ✅ Surface real failures to the user — toast, inline error, recovery action
- ❌ Never hide errors silently
- ❌ Never spam success toasts for obvious outcomes
What we ban in error handling
Section titled “What we ban in error handling”- 🚫
catch (error: any)— useunknown - 🚫
catch (e) { /* swallow */ }— even with a comment, this is a code smell - 🚫 Fallback values that hide root causes (e.g.
return [] // when query fails) - 🚫
try/catchwrapping a single call — only wrap when you have a meaningful recovery - 🚫 Re-throw without context — wrap with new error message + cause
Backend HTTP errors
Section titled “Backend HTTP errors”- Use proper HTTP status codes (400 validation, 401 auth, 403 forbidden, 404 not found, 409 conflict, 422 unprocessable, 500 internal)
- Body shape:
{ error: { message, code, details? } } - Never expose stack traces to clients
- ALWAYS log the full error server-side
Railway log format
Section titled “Railway log format”Structured JSON. Railway captures it. Search uses these fields: level, time, message, error, stack, organizationId, userId, requestId.
Don’t add custom prefixes to JSON fields. Use the structured fields.
Related
Section titled “Related”- Data Access (Repository Pattern) — uses same logging pattern