fix(ENG-12540): prevent unhandled rejection cascade on ONNX load failure#37
fix(ENG-12540): prevent unhandled rejection cascade on ONNX load failure#37
Conversation
`void inFlight.finally(...)` discarded the promise returned by
`.finally()`. When `inFlight` rejects (e.g. onnxruntime-node native
binary fails to load), the `.finally()` promise also rejects with no
handler — causing an unhandled rejection. The NestJS global handler
wraps and re-emits it, creating a recursive cascade (~130 deep) on
every request that triggers defender. The growing error strings
consume memory, contributing to pod restarts.
The actual error still propagates correctly through `await inFlight`
→ `loadModel()` catch → caller's try/catch. The `.catch(() => {})`
only silences the duplicate rejection from the `.finally()` chain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR prevents an unhandled promise rejection cascade when the ONNX model load fails (e.g., native onnxruntime-node binary can’t load), by ensuring the promise created by the .finally() cleanup path is explicitly handled.
Changes:
- Replace
void inFlight.finally(...)withinFlight.finally(...).catch(() => {})to prevent an unhandled rejection from the.finally()chain on load failures.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add comment explaining why .catch(() => {}) is needed
- Add test that verifies no unhandledRejection is emitted when the
ONNX model fails to load
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="specs/onnx-classifier.spec.ts">
<violation number="1" location="specs/onnx-classifier.spec.ts:87">
P3: Ensure the global unhandledRejection listener is always removed even when the test fails. Wrap the test body in a try/finally so the listener is cleaned up on all exits.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Auto-approved: Fixes a critical production issue involving recursive error cascades and memory growth. The change is isolated, follows standard promise handling practices, and includes a regression test.
Context
When the ONNX model fails to load (e.g. on Alpine where
onnxruntime-nodenative binary can't load),void inFlight.finally(...)in_loadModel()creates an unhandled promise rejection. The NestJS global handler wraps and re-emits it, creating a recursive cascade ~130 levels deep on every request that triggers defender.The growing error strings (
"Unhandled rejection: [object Promise] reason: Error: Unhandled rejection: ..."× 130) consume memory continuously under production load, contributing to pod restarts.Fix
Replace
void inFlight.finally(...)withinFlight.finally(...).catch(() => {}).The
.catch(() => {})silences the duplicate rejection from the.finally()chain. The actual error still propagates correctly throughawait inFlight→loadModel()catch → caller's try/catch.One-line diff
🤖 Generated with Claude Code
Summary by cubic
Stop the unhandled rejection cascade when ONNX model loading fails, eliminating error storms and memory growth under load. Addresses ENG-12540 by catching the
.finally()rejection while keeping the original error flow intact..catch(() => {})to theinFlight.finally(...)chain; the original error still propagates viaawait inFlight.unhandledRejectionon model load failure and usestry/finallyto reliably clean up the listener; document the catch inline.Written for commit f11b785. Summary will update on new commits.