From 4fbe80a043c706da284f7fce57a7344a88a865b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 13:32:50 +0000 Subject: [PATCH] web: send config-save response before rebooting, not after a guessed delay A fixed delay() before ESP.restart() raced the async TCP send: over WiFi the response could still be unacked when the reboot tore the stack down, so the browser saw a connection reset instead of the 200 it earned even though the config (e.g. a new device name) had already been saved. Reboot now waits on the request's onDisconnect callback, paired with a Connection: close header, so it only fires once the client has actually received the response. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JRj2Dd1rqgfYLnVwnnD4op --- src/web/WebServer.h | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/web/WebServer.h b/src/web/WebServer.h index 2fe12612..95f10d56 100644 --- a/src/web/WebServer.h +++ b/src/web/WebServer.h @@ -1113,11 +1113,25 @@ class BatteryWebServer { auto ok = _makeOk(); ok["rebooting"] = rebootNeeded; - _sendJson(r, 200, ok); - if (rebootNeeded) { - delay(200); - ESP.restart(); + if (!rebootNeeded) { + _sendJson(r, 200, ok); + return; } + + // A fixed delay() before ESP.restart() races the AsyncTCP send: over + // WiFi the response can still be unacked (or not even handed to the + // radio yet) when the reboot tears down the stack, so the browser + // sees a mid-flight connection reset instead of the 200 it earned. + // onDisconnect only fires once this client's TCP connection has + // actually closed, which — with "Connection: close" prompting that + // closure right after the response is flushed — guarantees delivery + // before we restart. + String s; + serializeJson(ok, s); + AsyncWebServerResponse* resp = r->beginResponse(200, "application/json", s.c_str()); + resp->addHeader("Connection", "close"); + r->onDisconnect([]() { ESP.restart(); }); + r->send(resp); } // ── POST /api/mqtt/clear ─────────────────────────────────────────────────