Skip to content

Carry api_encryption_active across scanner reloads#365

Merged
bdraco merged 1 commit into
mainfrom
bugfix/preserve-api-encryption-on-reload
May 6, 2026
Merged

Carry api_encryption_active across scanner reloads#365
bdraco merged 1 commit into
mainfrom
bugfix/preserve-api-encryption-on-reload

Conversation

@bdraco
Copy link
Copy Markdown
Member

@bdraco bdraco commented May 6, 2026

What does this implement/fix?

load_device_from_storage was building the new Device from
defaults whenever scanner.reload(...) fired, dropping any
api_encryption_active value the mDNS browser had already
written. mDNS announces only fire on the service-record TTL —
typically a couple of minutes between Added/Updated
callbacks — so any reload triggered between announces (a
successful flash, an --only-generate run, an unrelated YAML
edit on a sibling, an atomic-save remove/re-add cycle) wiped
the value back to None for the gap window.

The frontend's getEncryptionState reads
api_encryption_active together with has_pending_changes and
renders the lock icon's four-state indicator. With
api_encryption_active=null and has_pending_changes=true
(which the post-flash refresh sets even on an OTA that
succeeded), the indicator flips into the Pending install
chip on a freshly-flashed encrypted device that's still
actively broadcasting its noise PSK on the wire. Reported as
"every encryption device that mDNS could see still says pending
install"; a backend restart cleared it because the next mDNS
announce after the fresh subscription repopulated the field.

Mirror the existing carry-forward shape used for state /
deployed_config_hash / ip_addresses: pull
api_encryption_active off previous when one is provided,
default to None on first load. apply_api_encryption
follows the "Device is the source of truth" rule from #75 and
dedupes against device.api_encryption_active rather than a
monitor-side cache, so seeding the value from previous
doesn't break the "next mDNS announce corrects mismatched
state" guarantee — the next genuine observation still hits the
callback path. Adds three regression tests covering the
truthy / "" / no-previous cases.

Related issue or feature (if applicable):

  • companion to the "fresh HA addon install shows Pending
    install on every encryption device" report addressed by
    Resolve build_info.json through ext_storage_path #364 (build_info.json path fix). Even with that fix in
    place, the brief reload window kept tripping the indicator
    on healthy devices.

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Frontend coordination

  • No frontend change needed
  • Companion frontend PR: esphome/device-builder-dashboard-frontend#

Checklist

  • The code change is tested and works locally.
  • Pre-commit hooks pass (ruff, codespell, yaml/json/python checks).
  • Tests have been added or updated under tests/ where applicable.
  • components.json has not been hand-edited (regenerate via script/sync_components.py if a sync is needed).
  • Architecture-level changes are reflected in docs/ARCHITECTURE.md and/or docs/API.md.

mDNS announces fire on the device's TTL refresh — typically a
couple of minutes between Added/Updated callbacks for a healthy
device. Anything that triggers a scanner.reload between
announces (a successful flash, an --only-generate run, an
unrelated YAML edit on a sibling, an atomic-save remove/re-add
cycle) was rebuilding the in-memory Device with default fields,
wiping a previously-truthy api_encryption_active back to None.

The frontend's getEncryptionState reads api_encryption_active
together with has_pending_changes and renders the lock icon's
four-state indicator. With api_encryption_active=null and
has_pending_changes=true (which the post-flash refresh sets
even on an OTA that succeeded), the indicator flips into the
'pending install' chip on a freshly-flashed encrypted device
that's still actively broadcasting its noise PSK on mDNS. The
user reported it as 'pending encryption install on every
encryption device that mDNS could see' — a restart of the
backend cleared it because the next mDNS announce after the
fresh subscription repopulated the field.

Mirror the existing carry-forward shape used for state /
deployed_config_hash / ip_addresses: pull
api_encryption_active off previous when one is provided,
default to None on first load. apply_api_encryption follows
the 'Device is the source of truth' rule from PR #75 and
dedupes against device.api_encryption_active rather than a
monitor-side cache, so seeding the value from previous
doesn't break the 'next mDNS announce corrects mismatched
state' guarantee — the next genuine observation still hits
the callback path.
@github-actions github-actions Bot added the bugfix Bug fix label May 6, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 6, 2026

Merging this PR will not alter performance

✅ 9 untouched benchmarks


Comparing bugfix/preserve-api-encryption-on-reload (49fec26) with main (179bf1f)

Open in CodSpeed

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.64%. Comparing base (179bf1f) to head (49fec26).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #365   +/-   ##
=======================================
  Coverage   98.64%   98.64%           
=======================================
  Files          52       52           
  Lines        5911     5912    +1     
=======================================
+ Hits         5831     5832    +1     
  Misses         80       80           
Flag Coverage Δ
py3.12 98.59% <100.00%> (+<0.01%) ⬆️
py3.14 98.64% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
esphome_device_builder/helpers/device_yaml.py 99.62% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bdraco bdraco marked this pull request as ready for review May 6, 2026 21:05
Copilot AI review requested due to automatic review settings May 6, 2026 21:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a device-state regression where api_encryption_active (observed via mDNS and used by the dashboard’s lock/encryption indicator) was being dropped back to None whenever the device scanner rebuilt Device objects during reloads, causing transient but confusing UI state.

Changes:

  • Carry forward api_encryption_active from the prior in-memory Device when load_device_from_storage(..., previous=...) is used (matching existing carry-forward behavior for other monitor-derived fields).
  • Update the load_device_from_storage docstring to include ip_addresses and api_encryption_active in the carry-forward contract.
  • Add regression tests covering truthy encryption values, empty-string plaintext confirmation, and the no-previous default (None).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
esphome_device_builder/helpers/device_yaml.py Preserves api_encryption_active across reloads by copying it from previous into the new Device.
tests/test_device_yaml.py Adds regression tests ensuring encryption-active state is carried over correctly (including "") and defaults to None on first load.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@bdraco bdraco merged commit 4905909 into main May 6, 2026
17 checks passed
@bdraco bdraco deleted the bugfix/preserve-api-encryption-on-reload branch May 6, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants