From 987da8f5c4c19cfc095b55cbcca1efd288824748 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Aug 2026 08:40:35 -0500 Subject: [PATCH] Treat resource exhaustion as transient in the generic pch flow too --- esphome/build_helpers/pch.py | 6 ++++++ tests/unit_tests/build_gen/test_espidf.py | 26 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/esphome/build_helpers/pch.py b/esphome/build_helpers/pch.py index fe414a94ca..aff553a087 100644 --- a/esphome/build_helpers/pch.py +++ b/esphome/build_helpers/pch.py @@ -71,6 +71,9 @@ _CCACHE_PCH_ENV = { # Both include forms: an angle include resolving under src/ must enter the # digest too; ones that do not resolve simply end the walk +# Compiler failures that clear on their own must not latch the .failed marker +_TRANSIENT_ERRORS = ("No space left", "Cannot allocate", "Resource temporarily") + _INCLUDE_RE = re.compile(rb'^\s*#\s*include\s+["<]([^">]+)[">]', re.MULTILINE) @@ -379,6 +382,9 @@ def prepare_pch( # This path latches, so keep the full compiler output recoverable _LOGGER.debug("Full pch compile output: %s", error) discard_pch(build_dir) + if any(m in error for m in _TRANSIENT_ERRORS): + # Resource exhaustion clears on its own; retry next build + return # Skip retries until a header/flag/backend-identity/command change failed_marker.write_text(checksum + "\n", encoding="utf-8") os.utime(header) diff --git a/tests/unit_tests/build_gen/test_espidf.py b/tests/unit_tests/build_gen/test_espidf.py index a01c35c18c..dca97c5fde 100644 --- a/tests/unit_tests/build_gen/test_espidf.py +++ b/tests/unit_tests/build_gen/test_espidf.py @@ -1052,3 +1052,29 @@ def test_prepare_pch_identity_unknown_discards(tmp_path: Path) -> None: prepare_pch() assert not stale.exists() assert not (dev / "build" / "esphome_pch.h.gch.sum").exists() + + +def test_prepare_pch_transient_compiler_failure_does_not_latch( + tmp_path: Path, +) -> None: + """ENOSPC-style failures clear on their own; no .failed marker.""" + from esphome.build_gen.espidf import prepare_pch + + dev = _make_pch_device(tmp_path, "dev_e") + CORE.build_path = dev + calls = [] + + def enospc(cmd, **kwargs): + calls.append(cmd) + return subprocess.CompletedProcess( + cmd, 1, "", "fatal error: No space left on device" + ) + + with ( + patch.object(CORE, "name", "test"), + patch("esphome.build_helpers.pch.subprocess.run", side_effect=enospc), + ): + prepare_pch() + prepare_pch() + assert not (dev / "build" / "esphome_pch.h.gch.failed").exists() + assert len(calls) == 2