Skip to content

fix: prevent GTK double-populate crash and KeyError in entity/callback cleanup#20

Merged
gensyn merged 2 commits into
mainfrom
copilot/fix-streamcontroller-crash-home-assistant
May 14, 2026
Merged

fix: prevent GTK double-populate crash and KeyError in entity/callback cleanup#20
gensyn merged 2 commits into
mainfrom
copilot/fix-streamcontroller-crash-home-assistant

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 7, 2026

Users experience a gtk_list_tile_split assertion SIGABRT when navigating back to a page with configured HA buttons. A previous fix only guarded the "not connected" path; the crash still fires when on_ready() is called twice in the same GTK idle cycle while connected (double page-load observed in logs).

GTK double-populate crash (base_core.py)

_load_domains / _load_entities used get_item_amount() / get_item_at() to determine whether repopulation was needed. Because GTK processes model writes lazily, both rapid calls see stale widget state, each triggers populate(), and GTK's internal list-tile state is corrupted.

Fix: replace GTK model reads with Python-level caches (_last_loaded_domains, _last_loaded_entities). The cache is written synchronously on the first call, so a second rapid call skips populate() entirely.

# before – reads from GTK model (can return stale data in same idle cycle)
if domains != self._get_current_domains():
    self.domain_combo.populate(domains, domain, trigger_callback=False)

# after – compares against Python cache, always consistent
old_domains = self._last_loaded_domains
self._last_loaded_domains = list(domains)
if domains != old_domains:
    self.domain_combo.populate(domains, domain, trigger_callback=False)

_last_loaded_entities is also reset to None inside on_change_domain when the domain actually changes, ensuring the entity combo is always repopulated for the new domain.

KeyError crashes in cleanup (home_assistant_backend.py)

  • remove_tracked_entity: direct self._entities[domain] access raises KeyError when a domain disappears after a reconnect/reload. set.remove() raises KeyError when the callback was never registered. Fixed with .get() chaining + early return on None + discard().
  • remove_action_ready_callback: set.remove() raises KeyError under the same race conditions. Fixed with discard().

Tests

New cases cover: unknown domain in remove_tracked_entity, unregistered callback in both remove methods, entity cache reset on domain change, and the double-populate prevention for both domain and entity combos.

Copilot AI linked an issue May 7, 2026 that may be closed by this pull request
…k cleanup

- Replace GTK model reads (_get_current_domains/_get_current_entities) with
  Python-level caches (_last_loaded_domains/_last_loaded_entities) in
  BaseCore._load_domains and BaseCore._load_entities to prevent the
  gtk_list_tile_split assertion failure when on_ready fires twice in the same
  GTK idle cycle (e.g. double page load on navigation)
- Reset _last_loaded_entities = None in on_change_domain when the domain
  changes so the entity combo is always repopulated for the new domain
- Fix remove_tracked_entity: use self._entities.get(domain, {}).get(entity_id)
  instead of self._entities[domain] to avoid KeyError on unknown domain; add
  early return when entity_settings is None; use discard instead of remove to
  avoid KeyError when action callback is not registered
- Fix remove_action_ready_callback: use discard instead of remove to avoid
  KeyError when callback is not registered
- Add tests for new edge cases: unknown domain, unregistered callback, domain
  cache reset, and double populate prevention

Agent-Logs-Url: https://github.com/gensyn/HomeAssistantPlugin/sessions/9be9980d-1f54-437e-aded-8f1fd67fdded

Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix StreamController crashing with HomeAssistant Plugin fix: prevent GTK double-populate crash and KeyError in entity/callback cleanup May 7, 2026
Copilot AI requested a review from gensyn May 7, 2026 20:08
Copilot finished work on behalf of gensyn May 7, 2026 20:08
@gensyn gensyn marked this pull request as ready for review May 14, 2026 17:52
Copilot AI review requested due to automatic review settings May 14, 2026 17:52
@gensyn gensyn merged commit b2a54c2 into main May 14, 2026
2 checks passed
@gensyn gensyn deleted the copilot/fix-streamcontroller-crash-home-assistant branch May 14, 2026 17:53
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 pull request hardens the Home Assistant action UI against a GTK crash caused by rapid double-population of combo models, and makes backend cleanup methods resilient to race conditions that previously raised KeyError.

Changes:

  • Replace GTK model state reads in BaseCore._load_domains() / _load_entities() with Python-level “last loaded” caches to prevent double populate() calls in the same GTK idle cycle.
  • Make backend deregistration paths idempotent by using .get(...) lookups and set.discard() in remove_tracked_entity() and remove_action_ready_callback().
  • Add/adjust unit tests covering unknown domain cleanup, missing callback removal, entity cache reset on domain change, and double-populate prevention.

Reviewed changes

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

Show a summary per file
File Description
actions/cores/base_core/base_core.py Adds _last_loaded_domains/_last_loaded_entities caches, resets entity cache on domain change, and prevents redundant populate() calls.
backend/home_assistant_backend.py Avoids KeyError during callback/entity cleanup by switching to discard() and safe .get() chaining with early returns.
test/backend/test_backend_remove_tracked_entity.py Adds coverage for removing tracked entities when the domain is missing or the callback wasn’t registered.
test/backend/test_backend_remove_action_ready_callback.py Adds coverage for removing a callback that was never registered.
test/actions/cores/base_core/test_base_core_on_change_domain.py Verifies that changing the domain resets the entity cache so entities will repopulate.
test/actions/cores/base_core/test_base_core_load_entities.py Updates tests to rely on the new cache behavior; adds a “no double populate on repeated call” case.
test/actions/cores/base_core/test_base_core_load_domains.py Updates tests to rely on the new cache behavior; adds a “no double populate on repeated call” case.

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StreamController crashing with HomeAssistant Plugin

3 participants