{"id":"82f1063cce5c207e","repo":"aio-libs/aiohttp","slug":"response-is-sent-already-cannot-send-another-resp","errorCode":null,"errorMessage":"Response is sent already, cannot send another response with the error message","messagePattern":"Response is sent already, cannot send another response with the error message","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_protocol.py","lineNumber":864,"sourceCode":"    def handle_error(\n        self,\n        request: BaseRequest,\n        status: int = 500,\n        exc: BaseException | None = None,\n        message: str | None = None,\n    ) -> StreamResponse:\n        \"\"\"Handle errors.\n\n        Returns HTTP response with specific status code. Logs additional\n        information. It always closes current connection.\n        \"\"\"\n        self.log_exception(\n            \"Error handling request from %s\", request.remote, exc_info=exc\n        )\n\n        # some data already got sent, connection is broken\n        if request.writer.output_size > 0:\n            raise ConnectionError(\n                \"Response is sent already, cannot send another response \"\n                \"with the error message\"\n            )\n\n        ct = \"text/plain\"\n        if status == HTTPStatus.INTERNAL_SERVER_ERROR:\n            title = f\"{HTTPStatus.INTERNAL_SERVER_ERROR.value} {HTTPStatus.INTERNAL_SERVER_ERROR.phrase}\"\n            msg = HTTPStatus.INTERNAL_SERVER_ERROR.description\n            tb = None\n            if self._loop.get_debug():\n                with suppress(Exception):\n                    tb = traceback.format_exc()\n\n            if \"text/html\" in request.headers.get(\"Accept\", \"\"):\n                if tb:\n                    tb = html_escape(tb)\n                    msg = f\"<h2>Traceback:</h2>\\n<pre>{tb}</pre>\"\n                message = (","sourceCodeStart":846,"sourceCodeEnd":882,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_protocol.py#L846-L882","documentation":"Raised as ConnectionError by RequestHandler.handle_error (aiohttp/web_protocol.py:864) when an error occurs in the handler but request.writer.output_size > 0 - i.e. bytes have already been written to the wire. aiohttp cannot send a second response with the error status on the same connection (the status line and headers were already flushed), so it raises ConnectionError to signal the connection is unrecoverable and must be torn down rather than emit a malformed response.","triggerScenarios":"A handler calls resp.write(chunk) (or yield s in a StreamResponse body, or prepares a response) and then raises an exception afterwards - e.g. a streaming endpoint that emits part of a JSON document and then hits a DB error. handle_error is invoked by the error path, sees output_size > 0, and raises this ConnectionError.","commonSituations":"Streaming JSON / SSE / chunked responses where a downstream failure happens mid-stream; handlers that begin writing before they have fully validated work; partial HTML renders that fail in the middle of a template.","solutions":["Buffer the full response and only flush once all work has succeeded (prepare data first, then write).","If you must stream, design a protocol that tolerates truncation (e.g. SSE 'event: error' frames) instead of relying on aiohttp to emit a clean error.","Catch anticipated errors in the handler itself before any bytes are written so handle_error never runs into the output_size > 0 branch.","Log ConnectionError from handle_error at debug, since the client already has partial data."],"exampleFix":"// before\nasync def handler(request):\n    resp = web.StreamResponse()\n    await resp.prepare(request)\n    await resp.write(b\"{\\\"data\\\":\")\n    rows = await db.fetch()  # raises here -> ConnectionError\n    await resp.write(json.dumps(rows).encode())\n    return resp\n\n# after\nasync def handler(request):\n    rows = await db.fetch()          # fail before writing\n    return web.json_response({\"data\": rows})","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def has_sent_bytes(resp: web.StreamResponse) -> bool:\n    return getattr(resp, \"prepared\", False)","tryCatchPattern":"try:\n    return await handler(request)\nexcept ConnectionError:\n    logger.debug(\"client disconnect after partial write\")\n    raise","preventionTips":["Buffer the full response before flushing when downstream work can fail.","Validate inputs before any call to resp.write() / resp.prepare().","Design streaming protocols with in-band error frames (SSE 'event: error').","Do not rely on aiohttp's error handler to recover once bytes are on the wire."],"tags":["http","streaming","error-handling","connection"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}