From 150a65de8c152daaf37794259dfd047d2c727ddd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 May 2026 01:31:21 -0700 Subject: [PATCH] [store_yaml] Force-load deferred includes; cover secrets.yml - Promote `_force_load_include_files` from bundle.py to yaml_util.py as `force_load_include_files`. bundle.py and the new caller now share it. - Call it inside the `track_yaml_loads` block in config.py so deferred `!include` values, packages, and similar are loaded while the listener is active. Without this, `CORE.data["yaml_sources"]` only contained the entry YAML, and store_yaml shipped an incomplete recovery blob. - Treat both `secrets.yaml` and `secrets.yml` as secrets (matches `esphome.const.SECRETS_FILES`); also check the original (non-resolved) filename to catch a `secrets.yaml` symlinked to an unrelated target. - Update bundle tests for the new import path and message wording. --- esphome/components/store_yaml/__init__.py | 7 +++++-- esphome/config.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/esphome/components/store_yaml/__init__.py b/esphome/components/store_yaml/__init__.py index 733ccd65ef..fa25f36592 100644 --- a/esphome/components/store_yaml/__init__.py +++ b/esphome/components/store_yaml/__init__.py @@ -6,7 +6,7 @@ import struct import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_RAW_DATA_ID +from esphome.const import CONF_ID, CONF_RAW_DATA_ID, SECRETS_FILES from esphome.core import CORE, EsphomeError, HexInt _LOGGER = logging.getLogger(__name__) @@ -69,7 +69,10 @@ def _gather_files(include_secrets: bool) -> list[tuple[str, bytes]]: continue seen.add(resolved) - is_secret = resolved.name == "secrets.yaml" + # Either secrets.yaml or secrets.yml. Symlinks are followed by resolve() + # above, so we re-check the original path's name too in case someone + # symlinks `secrets.yaml` to a differently-named target. + is_secret = resolved.name in SECRETS_FILES or Path(path).name in SECRETS_FILES if is_secret and not include_secrets: content = REDACTED_PLACEHOLDER else: diff --git a/esphome/config.py b/esphome/config.py index 5676573d77..a82e41242a 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1194,8 +1194,11 @@ def _load_config( try: with yaml_util.track_yaml_loads() as loaded_files: config = yaml_util.load_yaml(CORE.config_path) - # Stash for components that want the on-disk YAML at codegen time - # (e.g. store_yaml embeds them into firmware for recovery). + # Resolve deferred !include / package references so the listener + # captures every reachable file. Components that want the on-disk + # YAML at codegen time (e.g. store_yaml for firmware recovery) + # read the list out of CORE.data["yaml_sources"] below. + yaml_util.force_load_include_files(config) CORE.data["yaml_sources"] = loaded_files except EsphomeError as e: raise InvalidYAMLError(e) from e