Skip to content

Remember the hostname a flash-free rename leaves on the device - #2814

Draft
bdraco wants to merge 11 commits into
mainfrom
rename-remember-deployed-hostname
Draft

bdraco wants to merge 11 commits into
mainfrom
rename-remember-deployed-hostname

Conversation

@bdraco

@bdraco bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member

What does this implement/fix?

Renaming a device without flashing it leaves the firmware answering to its old hostname, and we kept no record of that hostname. Every later install then targets <new>.local, which nothing answers to, so the device is stuck on the old name until you rename it back. That is the reported loop.

Three paths land there: a config-only rename (the offline confirmation), an in-place rename, and a plain esphome.name edit in the editor.

All three now record the pre-rename name as deployed_name in the device-metadata store, and the OTA address-cache args publish that name's cached addresses under the new name's key. It is only consulted when the device's own name resolves nothing, so a stale record is inert.

The record clears on the next landed flash, on a completed rename chain, on a rename back to it, and on an mDNS announce under the device's own name (so a flash from outside the dashboard heals it). It is dropped when another config claims that name, since whatever answers to it is then somebody else's board.

Device status is deliberately untouched: the device really is unreachable under its new name, so showing it offline is honest. The only thing missing was a way for the install to land.

Related issue or feature (if applicable):

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-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.index.json / definitions/components/*.json have 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.

The config-only and in-place rename paths rewrite the YAML without
flashing, so the firmware keeps broadcasting its pre-rename hostname
while the dashboard forgets it. Record that name as deployed_name in
the device-metadata store and publish its cached addresses under the
new name's OTA cache key when the new name resolves nothing; clear it
on the next landed flash, on a completed rename chain, or when a
rename targets that name again.
@github-actions github-actions Bot added the bugfix Bug fix label Sep 21, 2026
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@codspeed

codspeed Bot commented Sep 21, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 28 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing rename-remember-deployed-hostname (7fe579e) with main (78ff49b)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@codecov

codecov Bot commented Sep 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.75%. Comparing base (5a3c4c5) to head (7fe579e).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #2814   +/-   ##
=======================================
  Coverage   99.75%   99.75%           
=======================================
  Files         294      294           
  Lines       25317    25351   +34     
=======================================
+ Hits        25254    25288   +34     
  Misses         63       63           
Flag Coverage Δ
py3.12 99.71% <100.00%> (-0.01%) ⬇️
py3.14 99.59% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
...home_device_builder/controllers/_device_scanner.py 100.00% <100.00%> (ø)
...r/controllers/_device_state_monitor/api_reviver.py 100.00% <100.00%> (ø)
...ice_builder/controllers/devices/_metadata_store.py 100.00% <100.00%> (ø)
...e_device_builder/controllers/devices/controller.py 100.00% <100.00%> (ø)
...evice_builder/controllers/devices/firmware_sync.py 100.00% <100.00%> (ø)
...home_device_builder/controllers/devices/helpers.py 99.45% <100.00%> (+<0.01%) ⬆️
...ome_device_builder/controllers/devices/metadata.py 100.00% <100.00%> (ø)
...ce_builder/controllers/devices/mutations_simple.py 100.00% <100.00%> (ø)
..._device_builder/controllers/devices/scan_change.py 100.00% <100.00%> (ø)
...ice_builder/controllers/devices/state_callbacks.py 100.00% <100.00%> (ø)
... and 2 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

A direct esphome.name edit is the third flash-free rename, so stamp
deployed_name off the scan's name change too. Drop the record when
another config claims that name; whatever answers to it is no longer
this device, and targeting it would OTA the wrong board.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

The cold-start shallow to deep refine renames a package-supplied name
off its filename-stem placeholder, which no firmware answered to, so
only stamp for a device that was compiled at least once. Clear the
record when the device is heard over mDNS under its own name, so a
flash from outside the dashboard heals it.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

--only-generate populates loaded_integrations without flashing, so the
gate means the device has build output, not that it was ever flashed.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

An in-place rescan re-enters the scan-change stamp with the pre-rename
name, resurrecting a record the rename just cleared. Re-assert what the
rename resolved once the rescan is done; a no-op on every other path.

Also say the reclaimed-name record is ignored, not dropped: the read
skips it while another config owns the name, the entry stays on disk.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

@bdraco: @esphbot review

Re-reviewed. No commits since a1c78a91; verdict unchanged — merge-ready.

Re-verified the full ledger this pass: _commit_entry short-circuits unchanged entries, so the per-announce mDNS clear costs nothing. Chain, rename-back, and hand-edit-back cases all resolve correctly.

One carried-over suggestion: mutations_simple.py:347-350 re-asserts outside the write locks. Guard with if controller._metadata_store.get(new_filename):. Non-blocking.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

The re-assert runs outside the write locks, so a concurrent rename can
migrate the entry between the rescan and it, and a truthy record would
re-create an orphan row nothing reads.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Upload-only cleanup, incomplete mDNS signaling, duplicate-name handling, and a post-rescan race can discard or revive the fallback incorrectly.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity

Open (4)
What changed in this PR

Adds persistent tracking of pre-rename hostnames so OTA installs can reach devices after flash-free renames.

Changes:

  • Records and clears deployed_name metadata across rename, scan, mDNS, and flash paths.
  • Uses cached addresses for the deployed hostname during OTA.
  • Adds documentation and regression coverage.
File Description
esphome_device_builder/​controllers/​devices/​_metadata_store.py Adds deployed_name storage.
esphome_device_builder/​controllers/​devices/​controller.py Supplies deployed names to OTA cache lookup.
esphome_device_builder/​controllers/​devices/​firmware_sync.py Clears metadata after flashes and rename completion.
esphome_device_builder/​controllers/​devices/​helpers.py Resolves cached addresses through the old hostname.
esphome_device_builder/​controllers/​devices/​metadata.py Implements deployed-name stamping.
esphome_device_builder/​controllers/​devices/​mutations_simple.py Records names during flash-free renames.
esphome_device_builder/​controllers/​devices/​scan_change.py Detects manual hostname edits.
esphome_device_builder/​controllers/​devices/​state_callbacks.py Clears records on mDNS state updates.
tests/​controllers/​devices/​test_branches_coverage.py Covers scan-based recording.
tests/​controllers/​devices/​test_metadata_store.py Pins the new store field.
tests/​controllers/​devices/​test_rename_config_only.py Covers flash-free rename scenarios.
tests/​controllers/​devices/​test_rename_metadata.py Covers completed rename cleanup.
tests/​controllers/​firmware/​test_address_cache.py Covers old-hostname cache fallback.
tests/​controllers/​firmware/​test_refresh.py Covers post-flash cleanup.
tests/​test_device_state_event.py Covers mDNS and ping behavior.
docs/​API.md Documents rename behavior.
docs/​ARCHITECTURE.md Documents persistence and lifecycle design.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread esphome_device_builder/controllers/devices/controller.py Outdated
Comment thread esphome_device_builder/controllers/devices/firmware_sync.py Outdated
Comment thread esphome_device_builder/controllers/devices/state_callbacks.py Outdated
Comment thread esphome_device_builder/controllers/devices/mutations_simple.py Outdated
A bootloader-only upload replaces no app, so it no longer clears the
record. The mDNS self-heal moves onto the source change: apply() skips
the state callback when the device is already ONLINE via ping, which is
exactly the case the record keeps alive. The post-rescan re-assert
narrows to the clear, the only value a rescan can pollute, so it can't
overwrite a fresher mDNS or concurrent-rename value.
@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Previous review — superseded by a newer review below.

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Important issues found.

  • mDNS self-heal is skipped for the in-place / hand-edited rename — active_source survives the rename as MDNS

Five call sites open-coded the store key and its empty-means-cleared
sentinel. Add _clear_deployed_name beside _stamp_deployed_name and a
_deployed_name reader that owns the reclaimed-name rule, so the
deferred sweep reader inherits it instead of copying the guard. The
store gains a non-copying get_field, so the clear on every mDNS
ownership change no longer copies an entry to find the key absent.

Trim the docstrings and comments the record accumulated, hoist the
duplicated metadata-store test scaffolding into tests/conftest.py, and
move the lifecycle prose to the live-state section it belongs in.
A same-path rename carries runtime_state.active_source forward whole,
so after an in-place or hand-edited rename the row still reads mdns
while the ledger for the new name is unknown. The healing announce
flips the ledger but not the row, so the clear below the dedupe never
ran; the out-of-band flash it exists for was the case it missed.
@bdraco

bdraco commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

@esphbot review

@esphbot

esphbot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

PR Review — Remember the hostname a flash-free rename leaves on the device

The blocking finding from the previous round is fixed; what is left is reach and documentation, not correctness. Approving with nits.

The mDNS self-heal now sits above the active_source dedupe (state_callbacks.py:85-91) with a regression test that seeds active_source=MDNS first (test_mdns_ownership_clears_through_a_stale_active_source), so the in-place / hand-edited rename no longer strands the record. The rest holds up under re-reading: the --bootloader carve-out is right (bootloader flashes ride an UPLOAD job, factories.py:146-153, and --bootloader is UPLOAD-only in cli.py:90); the loaded_integrations gate keeps the cold-start refine from false-stamping; _clear_deployed_name reads before writing so it can never re-create a migrated-away entry; and the redirect is scoped tightly — .local only, only after the device's own name misses, ahead of the persisted-IP fallback, behind the reclaimed-name guard, and reusing default_mdns_address rather than rebuilding the key.

Pushing back on Copilot's first comment (controller.py:435): masking the record while another config owns the name is the better behaviour, not a bug. A YAML claiming a name is not evidence that a board answers it — the very common case is a user re-creating kitchen.yaml that is never flashed while the original board still answers kitchen.local. Clearing on claim would throw away a valid record; masking is recoverable.

  • Persisted-IP fallback is fragile for exactly these devices: the API reviver dials the old board, sees the pre-rename name, calls it a stale lease and wipes device.ip (api_reviver.py:209-224state_callbacks.py:153-164). In mDNS-dark deployments that leaves the redirect with nothing.
  • Once the renamed device settles into OFFLINE, an OTA install is deferred (factories.py:176-190) or its upload cancelled at terminality (lifecycle.py:191-218), so the new cache args never run on the config_only path. Teaching the reviver about deployed_name would fix both.
  • The post-rescan re-assert in _config_only_rename only exists because the rescan re-enters the stamp; suppressing the stamp for dashboard-driven renames would delete the branch and its remaining concurrent-in-place-rename race.
  • ARCHITECTURE's "keyed on the notification rather than on a state or source flip" overstates it — the callback is a ledger source flip; what it bypasses is the row's active_source dedupe.

Verification: findings are code traces over the PR head checkout; no test runner was available in the review worktree, so nothing here was observed at runtime.


✅ Resolved since last review (1)

Previously-flagged issues verified fixed
  • esphome_device_builder/controllers/devices/state_callbacks.py:85 mDNS self-heal is skipped for the in-place / hand-edited rename — active_source survives the rename as MDNS

🟢 Suggestions

1. The API reviver can wipe `device.ip` out from under this fallback for exactly the devices carrying a `deployed_name`
esphome_device_builder/controllers/devices/helpers.py:509-516

The redirect has two sources of addresses: the old name's zeroconf cache (new, lines 509-513) and the persisted device.ip (line 515). For a device that was renamed without a flash, the second one is actively destroyed by the API reviver a minute or so later.

Trace (controllers/_device_state_monitor/api_reviver.py):

  • After a config_only rename the new Device is built off a new path, so runtime_state.ip_addresses starts empty and <new>.local never resolves — shared.sweep_has_no_target (shared.py:85-92) is true, so the device is a revival candidate (_select_candidates, api_reviver.py:158-170).
  • The ICMP pre-filter succeeds (the board is alive at its old address), so the reviver dials device_info and compares reported != device.name (api_reviver.py:209). The firmware still reports the pre-rename name, so the comparison fails and monitor.invalidate_persisted_ip(...) runs — on_persisted_ip_invalidated then sets device.ip = "" in RAM and in the store (state_callbacks.py:153-164), plus an INFO line claiming the IP "now answers as 'kitchen'".

Why it matters: the IP was not stale — it is our device under its old hostname. In an mDNS-dark deployment (Docker bridge — the case the persisted IP exists for) there is no zeroconf entry for the old name either, so after the invalidation _build_address_cache_args returns [] and the install is back to targeting <new>.local, i.e. the bug this PR is fixing.

Remedy: give the reviver the same record this PR introduces — treat reported == deployed_name as an identity match rather than a mismatch (at minimum skip the invalidation; ideally revive, which would also address the deferral noted in the other finding). Not a blocker: the mDNS-capable path still works, and the invalidation predates this PR.

Verification note: this is a code trace, not an observed failure — no runnable test environment was present in the review worktree.

        if not addresses and is_local and deployed_name:
            # Publish the pre-rename name's IPs under the key the CLI looks up.
            stale = monitor.mdns.get_cached_addresses(default_mdns_address(deployed_name))
2. Cache args can't be exercised once the renamed device settles into OFFLINE — the install is deferred before it runs
esphome_device_builder/controllers/devices/controller.py:431-443

The record only pays off if an OTA job actually runs. For a device that has settled into DeviceState.OFFLINE — the state the PR description says is deliberately kept — no OTA job ever does:

  • factories.enqueue_install_or_defer (controllers/firmware/factories.py:176-190) turns an OTA install on a known-OFFLINE target into a compile-only job with is_deferred_install=True, arming queued_update for a wake that never comes: the device only ever announces its pre-rename name, which nothing tracks.
  • Even an install started during the UNKNOWN startup window gets converted at terminality — _defer_install_if_target_offline (controllers/firmware/lifecycle.py:191-218) flags the completed COMPILE and release_dependents cancels the held upload (lifecycle.py:122-125).

So the fix reaches the in-place / hand-edited-name case (that device keeps its RAM ip_addresses across a same-path reload, so the ping sweep holds it ONLINE and the install runs with the new cache args) but not the config_only rename of a device the dashboard already shows offline — which is the path the offline confirmation dialog exists for.

Remedy, and it is the same one as the reviver finding: let a recorded deployed_name count as identity evidence in api_reviver._verify_and_revive, so the device revives ONLINE under ping at its persisted IP; the install then runs and the cache args land it. Alternatively, exempt a config with a live deployed_name from the OFFLINE deferral gate.

Worth stating the reach in the PR body either way, since "the only thing missing was a way for the install to land" holds only for the still-reachable half.

        return _build_address_cache_args(
            device, self._state_monitor, self._deployed_name(configuration)
        )

Checklist

  • Prior warning (mDNS self-heal skipped behind the active_source dedupe) resolved
  • Every deployed_name write has a working clear path
  • Record written only off evidence the firmware carries that name
  • Bootloader-only upload exempted; INSTALL never carries the flag
  • Fallback targets the right device under name reuse
  • Fallback data survives long enough to be used — suggestion #1, suggestion #2
  • Persisted-format change is backward compatible
  • No save churn on the mDNS hot path
  • New branches have test coverage

Automated review by Kōan (Claude) HEAD=72a5693 8 min 46s

@esphbot esphbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tip

No blocking issues found — ready to merge.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Several paths can incorrectly clear or reactivate the hostname record, potentially leaving OTA unavailable or targeting the wrong device.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 5 Medium severity · 1 Low severity

Open (6)
Resolved since last review (4)

it is then somebody else's device, not ours.
"""
deployed_name = self._metadata_store.get_field(configuration, "deployed_name") or ""
return "" if self._devices_by_name(deployed_name) else deployed_name

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declined, deliberately. A YAML claiming a name is not evidence that a board answers it: the common case is re-creating kitchen.yaml that is never flashed while the original board still answers kitchen.local, and clearing on claim would destroy the only route back to it. Masking is recoverable, deleting is not. The docs say "ignored while another config owns that name", not dropped, so the contract matches the code. esphbot reached the same conclusion independently this round.

Comment on lines +442 to +443
deployed_name = self._metadata_store.get_field(configuration, "deployed_name") or ""
return "" if self._devices_by_name(deployed_name) else deployed_name

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0ee2d61, though by removing the raw read rather than validating it. _deployed_name now reads Device.deployed_name, which _resolve_device_metadata already coerces with str(), so a malformed persisted value can never reach _devices_by_name as an unhashable type.

return
recompute_hash = job_type in COMPILING_JOB_TYPES
flashed = job_type in (JobType.UPLOAD, JobType.INSTALL)
if flashed and not job.flash_bootloader:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declined, and esphbot pushed back on this one too. A rename migrates no build dir: the build tree is keyed on the device name, so after a rename there is no artifact under the new name until a compile, and that compile bakes in the new hostname. A standalone firmware/upload therefore either flashes an image built from the current YAML, or fails and never reaches this clear (only COMPLETED jobs do). The --bootloader carve-out covers the one upload that genuinely replaces no app.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declined; esphbot pushed back on this one too. A rename migrates no build dir: the build tree is keyed on the device name, so after a rename there is no artifact under the new name until a compile, and that compile bakes in the new hostname. A standalone firmware/upload therefore either flashes an image built from the current YAML, or fails and never reaches this clear (only COMPLETED jobs get here). The --bootloader carve-out covers the one upload that genuinely replaces no app.

Comment on lines 347 to +352
await rescan_renamed(controller, new_filename)
# An in-place rescan re-stamps the pre-rename name over a record this
# rename just cleared. Only the clear is re-asserted, so this can't
# overwrite a fresher mDNS or concurrent-rename value.
if not deployed_name:
controller._clear_deployed_name(new_filename)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged, not fixed. The clear is now conditional (only a cleared value is re-asserted), so this needs two concurrent in-place renames of the same device, and the loss is recoverable: the record re-heals on the next mDNS announce or is made moot by the next flash. Holding the locks across the rescan risks deadlock, since scan handlers run arbitrary work, and a true compare-and-swap needs a generation counter the store does not have. Not worth either for this window.

Comment on lines +84 to +85
for device in controller._devices_by_name(name):
if source is ReachabilitySource.MDNS:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch, fixed in 0ee2d61. The clear now only runs when the announced name maps to exactly one config; siblings sharing a name share the broadcast, so it cannot attribute which one was flashed. Covered by test_mdns_of_a_shared_name_keeps_both_deployed_names.

device, self._state_monitor, self._deployed_name(configuration)
)

def _deployed_name(self, configuration: str) -> str:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0ee2d61; _deployed_name moved down to the private-helper section.

Without this the reviver dials the persisted IP, reads the firmware's
pre-rename name, calls the lease stale and wipes device.ip, which is
the fallback the redirect leans on when mDNS is dark. Worse, the device
stays OFFLINE, so enqueue_install_or_defer turns the install into a
compile-only job armed for a wake that never comes; the record only
pays off if an OTA actually runs. Accepting the recorded name revives
it under ping and lets the install through.

Also only heal off an mDNS announce when the name maps to one config,
and move the deployed_name reader onto the Device row and down into
the private section.
Mark deployed_name serialize-omit: both readers are backend, nothing on
the frontend consumes it, and shipping it would make an internal record
a de-facto wire contract. Apply the reclaimed-name rule in the reviver
too, so the rule the docs state as a property of the record holds at
both readers rather than just one.

Hoist the mDNS clear out of a loop it can only traverse once, fold the
address fallback into the lookup shape above it, short-circuit the
empty record before the bucket lookup, coerce the stamp like the load
path does, and collapse the source-change tests into one table.
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.

De-Synced hostname [Bug]

4 participants