Skip to content

State.notify_del() can leave stale trigger queues registered after reload #876

Description

@ffrog8

Summary

State.notify_del() returns from the whole cleanup loop when it encounters a subscription that has already been removed.

This can happen when a trigger watches both an entity and one of its attributes, because both are registered under the same base-entity key.

The early return leaves later subscriptions attached to the old trigger queue. After the trigger task has stopped, state changes continue adding messages to that queue. Repeated reloads can therefore accumulate stale queues and duplicate notifications.

Changing the return to continue fixes the issue in my testing.

The defect is present in PyScript 2.0.1 and 2.1.0, and the same code is currently present on master.

Possibly related to #281, which also reports triggers surviving reloads, but this appears to be a separate concrete cleanup defect in State.notify_del().

Environment

Live reproduction and verification:

  • PyScript 2.0.1
  • Home Assistant Core 2026.7.3, container install
  • Python 3.14
  • State-triggered scripts importing a shared PyScript module

PyScript 2.1.0 and current master were also checked and contain the same notify_del() early-return logic.

Cause

For a watch set such as:

sensor.example
sensor.example.flag
sensor.other

notify_add() registers both sensor.example watches under the same sensor.example key.

During notify_del():

  1. The first entry removes the queue from sensor.example.
  2. The attribute entry finds that queue already absent.
  3. notify_del() executes return.
  4. sensor.other is never cleaned up.

The stopped trigger queue therefore remains registered for sensor.other.

Reproduction

Using the real State class:

  1. Register an old queue and another active queue against the three watches above.
  2. Call State.notify_del() with iteration order:
sensor.example
sensor.example.flag
sensor.other
  1. Check the notification registry.

With the current code, the old queue remains registered under sensor.other.

Sending a sensor.other update through State.update() also delivers a notification to that stopped queue.

Proposed fix

if state_var_name not in cls.notify or queue not in cls.notify[state_var_name]:
-    return
+    continue

An absent subscription only means there is nothing to remove for that particular watch. It does not mean cleanup of the remaining watches is complete.

Observed result

I reproduced this across script reloads on 2.0.1:

Baseline Reload 1 Reload 2
Registered queues, unpatched 94 96 98
Current trigger queues 94 94 94
Stale/unmatched queues 0 2 4
Registered queues, patched 94 94 94
Stale/unmatched queues, patched 0 0 0

Before the patch, the retained queues accumulated more than 30,000 queued notifications during the test.

After changing return to continue, restarting Home Assistant and repeating two reloads, I saw no stale queues or accumulated stale messages.

I also ran 10 focused cleanup tests against the patched installed package; all 10 passed.

Notes

The failure depends on watch iteration order, so it will not occur for every trigger.

The proposed change is deliberately narrow. Queue limits or periodic cleanup would only mask the retained-subscription problem rather than fix it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions