From eb7b522461922099d3d184dc67f6ff24ead88853 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 22 Aug 2026 22:36:43 -0500 Subject: [PATCH] [core] Replace a damaged existing file in write_file_if_changed --- esphome/helpers.py | 10 +++++++++- tests/unit_tests/test_helpers.py | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/esphome/helpers.py b/esphome/helpers.py index 9b2a461ccd..175f492dd6 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -550,9 +550,17 @@ def write_file_if_changed(path: Path, text: str) -> bool: Returns true if the file was changed. """ + from esphome.core import EsphomeError + src_content = None if path.is_file(): - src_content = read_file(path) + try: + src_content = read_file(path) + except (EsphomeError, UnicodeDecodeError) as err: + # A damaged existing file (unreadable, non-UTF-8) must be + # replaced, not abort the regeneration that would fix it + _LOGGER.warning("Replacing damaged file %s: %s", path, err) + path.unlink(missing_ok=True) if src_content == text: return False write_file(path, text) diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 6e00e5b80f..37f024d40f 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -1108,3 +1108,15 @@ def test_progressbar_enabled_on_pipe_with_dashboard(monkeypatch) -> None: def test_format_duration(seconds: float, expected: str) -> None: """Test that durations are rendered as short human-readable strings.""" assert helpers.format_duration(seconds) == expected + + +def test_write_file_if_changed_replaces_damaged_file(tmp_path, caplog) -> None: + """A non-UTF-8 or unreadable existing file is logged and overwritten; + aborting would block the very regeneration that fixes it.""" + from esphome.helpers import write_file_if_changed + + target = tmp_path / "generated.txt" + target.write_bytes(b"\xff\xfe") + assert write_file_if_changed(target, "fresh content") is True + assert target.read_text(encoding="utf-8") == "fresh content" + assert "Replacing damaged file" in caplog.text