Skip to content

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 include stack:. The 2026-03-25 S3 SignatureDoesNotMatch bug took hours to diagnose because logs didn’t have stack traces.

Use log from server/lib/logger. Enforced by ESLint rule.

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.

FieldPurpose
Message prefix [PREFIX]Context tag (e.g. [SIDEKICK-CHAT], [BRAIN-SEARCH], [COMPOSIO])
What failedShort imperative — “Failed to fetch user”, “Migration query timed out”
errorNormalized error message via getErrorMessage(e)
stackFull stack trace — required
organizationIdFor org-scoped operations
userIdIf user-attributed
...contextAnything else relevant — request ID, params, durations

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;
}
LevelWhen
log.debugVerbose, dev-only context
log.infoNotable events (“Migration applied”, “Sidekick session started”)
log.warnRecoverable issues (“Retry triggered”, “Cache miss”)
log.errorFailures — always with stack
log.fatalUnrecoverable — process exit imminent

Default to log.info for hot paths. Don’t paper the logs with log.debug that gets disabled in prod.

  • 🚫 Don’t console.error in 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
  • 🚫 catch (error: any) — use unknown
  • 🚫 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/catch wrapping a single call — only wrap when you have a meaningful recovery
  • 🚫 Re-throw without context — wrap with new error message + cause
  • 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

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.