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.
| Input | HTTP |
|---|---|
HttpException | Its status |
BadRequestException with an array | 400. Translated messages in the body only when debug is on |
| Any other throw | 500, generic internal error message |
| Status >= 500 | Logged and sent to Sentry |
Validation pipe failures are 400 before the controller runs. Unknown properties are rejected.
Where the row stands
| Failure | Business row | Retry |
|---|---|---|
Throw inside prisma.$transaction before commit | Rolled back, including the outbox insert | The client or scheduler must retry the original operation |
| Outbox listener throws | Already committed | Dispatcher, max 10, then DEAD |
| Bull job throws | Depends on the job. Many jobs mutate before they throw | Queue attempts, then a retained failed job and Sentry on the terminal attempt |
| In-process emit with no listener | Producer already committed | Nothing 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
| System | Observed behavior |
|---|---|
| Marketplace sync | Bull retries (3), then the circuit breaker sets the channel to SUSPENDED |
| Inventory publish | Queue retry, per-item success: false, feed poll reaper for async feeds |
| Courier label | Fulfillment can sit in LABEL_CREATION_FAILED and move back to LABEL_CREATING |
| S3 | Boot fails if required settings are missing. Runtime S3 errors were not catalogued |
| SMTP | Email job retries. Forgot-password still returns success to the client |
| AI gateway | Boot continues. Embeddings degrade. Workspace routes 403 until AI_WORKSPACE_ENABLED=true |
| Bharat Address | The address job fails and retries if the processor throws. Fallback address status is unknown |
| Unknown channel type | Scheduler 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
$transactionis 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:
WalletListenerlogs and thenthrow 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.- Two copies of the order state machine. They match today.
isValidOrderTransitionreturns true when the current status is null, which allows any initial status.return.status.changedandreturn.stock.restoredhave no listener.StockMovementuniqueness includescreatedAt.- Midnight cron only logs.
- Courier seed codes
NIP,PIK,SHYP,ARMX,AMZhave no strategy class. - Refresh-token is public and relies on
JwtRefreshGuardonly. - Permissions default to allow when the decorator is omitted.
- Raw SQL bypasses the tenancy extension unless the SQL filters
tenantId. /metricsis unauthenticated in the application. Network policy is unknown.- 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.