When a controller handler sends a response and then throws an error, NestJS's default exception filter detects that headers are already sent and attempts to gracefully close the response via adapter.end(response). Our adapter delegates this to UwsResponse.send(), which throws Error: Response already sent because this.finished is already true.
This throw bubbles into RouteRegistry.handleException() and gets logged as an "Unhandled route error", even though the client already received their response successfully and the server doesn't actually crash.
Steps to Reproduce
@Controller('test')
class TestController {
@Get('after-send')
afterSend(@Res() res: UwsResponse) {
res.status(200).json({ ok: true });
throw new Error('Error after response sent');
}
}
Expected: Server silently continues; client gets 200 { ok: true }.
Actual: Server logs a spurious "Unhandled route error: Error: Response already sent" stack trace.
Root Cause
// src/http/core/response.ts:1270
if (this.finished) {
throw new Error('Response already sent');
}
This is stricter than standard HTTP framework behavior. When a response is already finished, send() should be a no-op rather than throwing.
Fix
if (this.finished) {
return; // Silent no-op
}
When a controller handler sends a response and then throws an error, NestJS's default exception filter detects that headers are already sent and attempts to gracefully close the response via
adapter.end(response). Our adapter delegates this toUwsResponse.send(), which throws Error: Response already sent becausethis.finishedis alreadytrue.This throw bubbles into
RouteRegistry.handleException()and gets logged as an "Unhandled route error", even though the client already received their response successfully and the server doesn't actually crash.Steps to Reproduce
Expected: Server silently continues; client gets 200 { ok: true }.
Actual: Server logs a spurious "Unhandled route error: Error: Response already sent" stack trace.
Root Cause
This is stricter than standard HTTP framework behavior. When a response is already finished,
send()should be a no-op rather than throwing.Fix