Fix ignore_device_delete: TypeError on device leave, option lookup never matched#2935
Merged
asgothian merged 1 commit intoJul 10, 2026
Merged
Conversation
insertedDeviceLeave is the spliced deviceLeave listener that is armed at
every controller start. As soon as any device or model has the
'ignore Delete' option set (that is the optionExists gate), the next
deviceLeave for a resolvable device threw:
- entity.model does not exist on any resolveEntity() return shape
(device/group/coordinator all return {type, device, mapped, ...}),
so entity.model.modelID is a TypeError inside an async event
listener - an unhandled rejection instead of an ignored leave.
Independently, the option lookup itself could never match:
getDeviceOption(device_id, model_id, option, dOnly) was called with
3 arguments, so it looked up the option key `false` and returned
undefined - the leave was never ignored even without the crash.
Now the option is read as getDeviceOption(ieee, entity.mapped?.model,
'ignore_device_delete', true), which resolves the per-device option and
the per-model option (the option is declared byDevice and byModel), and
the log prints the existing entity.device.modelID.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
insertedDeviceLeaveis the spliceddeviceLeavelistener that is armed at every controller start. As soon as any device or model has the ignore Delete option set (that is exactly what theoptionExists('ignore_device_delete')gate checks), the nextdeviceLeavefor a resolvable device throws instead of ignoring the leave:entity.modeldoes not exist on anyresolveEntity()return shape — the device, group and coordinator branches all return{type, device, mapped, endpoint, ...}without amodelkey.entity.model.modelIDis therefore a guaranteedTypeError, raised inside an async event listener → unhandled rejection.Independently of the crash, the option lookup could never match:
getDeviceOption(device_id, model_id, option, dOnly)was called with three arguments, sooptionwas the literalfalse— the lookup returnedundefined, and(undefined ?? {}).ignore_device_deleteis always falsy. The feature was inert even where it did not crash.The fix
dOnly=trueso both the per-device and the per-model option resolve (the option is declaredbyDevice: true, byModel: truein the catalogue)entity.mapped?.modelis theby_modelkey space the local config stores options under (common.type);?.keeps unsupported devices safe —getDeviceOptionhandles an undefined modelentity.device.modelIDVerification
Functional test against the real
lib/localConfig.js: the old 3-argument form provably returnsundefinedfor a set option (per-device, stored under.optionsthe wayupdateConfigItemspersists it), the new call resolves the per-device option, the per-model option, and returnsundefinedfor an unsupported device without a mapped model.node --check, ESLint andnpm testgreen.Note: #2934 touches the same warn line (it adds the model parameter to that
devLabelcall). Whichever lands second I will rebase promptly — the overlap is that one line.🤖 Generated with Claude Code