From a1a60c44da41c01be280d0eac59a27d7be2a2723 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 12:48:34 -0600 Subject: [PATCH 1/2] [web_server_base] Update ESPAsyncWebServer to 3.9.6 (#13639) --- .clang-tidy.hash | 2 +- esphome/components/web_server_base/__init__.py | 2 +- platformio.ini | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 1cb5f98c28..ab354259e3 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab +069fa9526c52f7c580a9ec17c7678d12f142221387e9b561c18f95394d4629a3 diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 6c756575d4..6326b4d6ff 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -53,4 +53,4 @@ async def to_code(config): "lib_ignore", ["ESPAsyncTCP", "AsyncTCP", "AsyncTCP_RP2040W"] ) # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json - cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.5") + cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6") diff --git a/platformio.ini b/platformio.ini index 0f5bf2f8fb..bb0de3c2b1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -114,7 +114,7 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base makuna/NeoPixelBus@2.7.3 ; neopixelbus ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) @@ -202,7 +202,7 @@ lib_deps = ${common:arduino.lib_deps} ayushsharma82/RPAsyncTCP@1.3.2 ; async_tcp bblanchon/ArduinoJson@7.4.2 ; json - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base build_flags = ${common:arduino.build_flags} -DUSE_RP2040 @@ -218,7 +218,7 @@ framework = arduino lib_compat_mode = soft lib_deps = bblanchon/ArduinoJson@7.4.2 ; json - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base droscy/esp_wireguard@0.4.2 ; wireguard build_flags = ${common:arduino.build_flags} From 4e96b20b46f0ff59c75d549e0ad11f0d9fb287f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 12:49:14 -0600 Subject: [PATCH 2/2] [mqtt] Restore ESP8266 on_message defer to prevent stack overflow (#13648) --- esphome/components/mqtt/mqtt_client.cpp | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index e7364f3406..a284b162dd 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -643,10 +643,34 @@ static bool topic_match(const char *message, const char *subscription) { } void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { - for (auto &subscription : this->subscriptions_) { - if (topic_match(topic.c_str(), subscription.topic.c_str())) - subscription.callback(topic, payload); - } +#ifdef USE_ESP8266 + // IMPORTANT: This defer is REQUIRED to prevent stack overflow crashes on ESP8266. + // + // On ESP8266, this callback is invoked directly from the lwIP/AsyncTCP network stack + // which runs in the "sys" context with a very limited stack (~4KB). By the time we + // reach this function, the stack is already partially consumed by the network + // processing chain: tcp_input -> AsyncClient::_recv -> AsyncMqttClient::_onMessage -> here. + // + // MQTT subscription callbacks can trigger arbitrary user actions (automations, HTTP + // requests, sensor updates, etc.) which may have deep call stacks of their own. + // For example, an HTTP request action requires: DNS lookup -> TCP connect -> TLS + // handshake (if HTTPS) -> request formatting. This easily overflows the remaining + // system stack space, causing a LoadStoreAlignmentCause exception or silent corruption. + // + // By deferring to the main loop, we ensure callbacks execute with a fresh, full-size + // stack in the normal application context rather than the constrained network task. + // + // DO NOT REMOVE THIS DEFER without understanding the above. It may appear to work + // in simple tests but will cause crashes with complex automations. + this->defer([this, topic, payload]() { +#endif + for (auto &subscription : this->subscriptions_) { + if (topic_match(topic.c_str(), subscription.topic.c_str())) + subscription.callback(topic, payload); + } +#ifdef USE_ESP8266 + }); +#endif } // Setters