From 81a2caee36e2ad3d774b020141ae70cdcf870107 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2026 12:41:12 -1000 Subject: [PATCH] [web_server] Reset OTA backend on new upload to avoid bricked device after interrupted OTA If a captive_portal or web_server OTA upload is interrupted mid-stream (e.g. TCP connection reset), the shared OTARequestHandler's ota_backend_ is left open. When the browser resends the multipart POST, the guard 'index == 0 && !ota_backend_' skipped re-initialization, so new bytes were written at the previous session's offset. The Updater's end() then reports success with a concatenated image in flash, bricking the device on reboot. Abort and reset any in-progress backend whenever a new multipart upload starts (index == 0) so the fresh upload begins a clean OTA session. --- .../components/web_server/ota/ota_web_server.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 95b166901a..d5c6f9e7a4 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -114,7 +114,18 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf uint8_t *data, size_t len, bool final) { ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK; - if (index == 0 && !this->ota_backend_) { + if (index == 0) { + // A new multipart upload is starting. If a previous upload was interrupted + // (e.g. TCP reset) the backend from that session may still be open; tear it + // down so flash state doesn't get concatenated with the new image (which + // can produce a technically-valid-sized but corrupted firmware that + // bricks the device once it reboots). + if (this->ota_backend_) { + ESP_LOGW(TAG, "New OTA upload received while previous session was still open; aborting previous session"); + this->ota_backend_->abort(); + this->ota_backend_.reset(); + } + // Initialize OTA on first call this->ota_init_(filename.c_str());