Skip to main content

Error handling

ResponseExceptionFilter (@Catch()) translates throws. Messages go through MessageService (i18n key or default). Debug stacks and validation arrays are included only when app.debug is true.

InputHTTP
HttpExceptionIts status
BadRequestException with an array400. Translated messages in the body only when debug is on
Any other throw500, generic internal error message
Status >= 500Logged and sent to Sentry

Validation pipe failures are 400 before the controller runs. Unknown properties are rejected.

Where the row stands​

FailureBusiness rowRetry
Throw inside prisma.$transaction before commitRolled back, including the outbox insertThe client or scheduler must retry the original operation
Outbox listener throwsAlready committedDispatcher, max 10, then DEAD
Bull job throwsDepends on the job. Many jobs mutate before they throwQueue attempts, then a retained failed job and Sentry on the terminal attempt
In-process emit with no listenerProducer already committedNothing retries

One listener throwing fails the whole outbox emit. Listeners that already ran will run again on retry unless they opted into IdempotentEventHandlerService.

External systems​

SystemObserved behavior
Marketplace syncBull retries (3), then the circuit breaker sets the channel to SUSPENDED
Inventory publishQueue retry, per-item success: false, feed poll reaper for async feeds
Courier labelFulfillment can sit in LABEL_CREATION_FAILED and move back to LABEL_CREATING
S3Boot fails if required settings are missing. Runtime S3 errors were not catalogued
SMTPEmail job retries. Forgot-password still returns success to the client
AI gatewayBoot continues. Embeddings degrade. Workspace routes 403 until AI_WORKSPACE_ENABLED=true
Bharat AddressThe address job fails and retries if the processor throws. Fallback address status is unknown
Unknown channel typeScheduler logs a warning and continues. That channel is not synced

Circuit breaker in sync-job.processor.ts: 5 consecutive non-transient failures of one sync type inside 6 hours suspend the channel. Auth-shaped failures suspend after 2 inside that window. The comment says auth-shaped failures still need corroboration. Read evaluateCircuitBreaker before you describe the exact error classes. Jobs still retry inside Bull before that history accumulates.

Partial success​

  • A single aggregate inside one $transaction is atomic.
  • Outbox plus aggregate is atomic only when enqueue uses the same tx.
  • Label purchase at a courier is not in the same transaction as the database. A crash after the courier accepts and before the local commit can leave an external shipment with no local row, or the reverse. Compensating cancel was not verified per carrier.
  • Bulk inventory publish returns per-item results. A chunk can contain both successes and failures.
  • Settlement import can record a batch status while individual lines fail. Line-level commit rules are unknown.

Known risks​

Documented as behavior, not as a fix:

  1. WalletListener logs and then throw error. The catch comment says not to rethrow. Actual behavior is rethrow, so the outbox retries the debit. Ledger uniqueness for one order token debit was not proven. A retry can double-charge if the first debit committed and the throw happened afterward.
  2. Two copies of the order state machine. They match today.
  3. isValidOrderTransition returns true when the current status is null, which allows any initial status.
  4. return.status.changed and return.stock.restored have no listener.
  5. StockMovement uniqueness includes createdAt.
  6. Midnight cron only logs.
  7. Courier seed codes NIP, PIK, SHYP, ARMX, AMZ have no strategy class.
  8. Refresh-token is public and relies on JwtRefreshGuard only.
  9. Permissions default to allow when the decorator is omitted.
  10. Raw SQL bypasses the tenancy extension unless the SQL filters tenantId.
  11. /metrics is unauthenticated in the application. Network policy is unknown.
  12. MCP websocket authentication on port 3002 was not established.

Courier services, especially Shiprocket, contain many catch blocks. Several update local shipment state or enqueue a webhook from inside the catch. Do not describe a carrier call as one transaction with the local database.

HTTP development guide: Validation and errors.