Skip to content

fix: restore idempotent double-registration for intent-topic wrapped handlers - #421

Merged
JarbasAl merged 1 commit into
devfrom
fix/intent-registration-idempotency
Aug 14, 2026
Merged

fix: restore idempotent double-registration for intent-topic wrapped handlers#421
JarbasAl merged 1 commit into
devfrom
fix/intent-registration-idempotency

Conversation

@JarbasAl

Copy link
Copy Markdown
Member

🤖 Auto-generated by Claude Fable 5 (claude-fable-5) via Claude Code — NOT human-reviewed. Verify before acting.

Registering the same handler twice on the same intent topic used to be harmless. pyee keys its listener OrderedDict by the handler object itself, so two on() calls with an equal bound method collapsed onto one slot and the handler only fired once.

The intent-topic bridge in FakeBus.on / AsyncFakeBus.on broke that by wrapping every intent-topic registration in a fresh closure. pyee then saw two distinct wrapper objects for what was meant to be one subscription, and the handler fired twice for a single dispatch. This surfaced in ovos-workshop as test_dual_registration_does_not_double_fire failing with 2 != 1.

This restores the old behavior: on() now reuses the existing wrapper for a repeat (msg_type, handler) pair instead of minting a new one, so re-registering re-adds the same closure pyee already knows. remove() already worked off the same per-handler registration map so it needed no change. Genuinely different handlers on the same topic still both fire, and non-intent topics are untouched. Added 6 regression tests covering same-handler-twice-plus-off, same-handler-both-spellings, two-different-handlers, and the non-intent-topic control, for both FakeBus and AsyncFakeBus; the full ovos-utils suite (955 passed, 1 skipped) and the ovos-workshop reproducer both pass with the fix.

@github-actions github-actions Bot added the fix label Aug 14, 2026
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0afd4dd-f0e3-4bfb-9e00-0b3b0950aa34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

The automated sentinels have completed their watch. 💂‍♂️

I've aggregated the results of the automated checks for this PR below.

🔍 Lint

Beep boop! Standard processing sub-routine complete. 🦾

ruff: issues found — see job log

🔒 Security (pip-audit)

Ensuring our cross-site scripting defenses are up. 🛡️

✅ No known vulnerabilities found (47 packages scanned).

🏷️ Release Preview

I've checked the 'Legal' section for the release. ⚖️

Current: 0.13.10a2Next: 0.13.11a1

Signal Value
Label fix
PR title fix: restore idempotent double-registration for intent-topic wrapped handlers
Bump build

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 0.13.11a1

Channel Status Note Current Constraint
Stable Too new (must be <0.9.0) ovos-utils>=0.8.1,<0.9.0
Testing Too new (must be <0.8.5) ovos-utils>=0.8.4,<0.8.5
Alpha Compatible ovos-utils>=0.13.10a2

📋 Repo Health

The repo's annual physical is complete! 🩺

✅ All required files present.

Latest Version: 0.13.10a2

ovos_utils/version.py — Version file
README.md — README
LICENSE — License file
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
ovos_utils/version.py has valid version block markers

📊 Coverage

Testing the limits! Here's the coverage breakdown. 📏

85.3% total coverage

Files below 80% coverage (5 files)
File Coverage Missing lines
ovos_utils/log_parser.py 48.4% 225
ovos_utils/__init__.py 63.6% 16
ovos_utils/file_utils.py 74.4% 56
ovos_utils/thread_utils.py 76.9% 12
ovos_utils/geolocation.py 78.4% 22

Full report: download the coverage-report artifact.

🔨 Build Tests

I tried building your changes, and here's what happened! 🔨

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

⚖️ License Check

Checking for any potential license conflicts. ⚔️

✅ No license violations found.

Policy: Apache 2.0 (universal donor). StrongCopyleft / NetworkCopyleft / WeakCopyleft / Other / Error categories fail. MPL allowed.


Keeping things running like clockwork 🕰️

Registering the same handler twice on one guarded topic used to be
harmless because pyee keys its listener OrderedDict by the handler
object, so an equal bound method collapsed onto one slot. Both the
intent-topic bridge and the namespace-migration (is_migrated) branch
of the mirror guard started wrapping every registration in a fresh
closure, and pyee saw a new object each time, so the same registration
fired twice instead of once.

FakeBus.on/AsyncFakeBus.on already reused the existing wrapper for a
repeat (msg_type, handler) pair; once() bypassed the mirror guard
entirely, so a handler bound via once() to both spellings of a
mirrored dispatch fired twice, and a later on() of the same handler
stacked a second, independent listener because once() never recorded
itself in _dedup_registrations. once() now goes through the same
guard-selection and wrapper-reuse machinery as on(), self-cleaning its
bookkeeping once pyee auto-removes the fired listener.

Adds regression coverage for once() on both intent spellings, once()
followed by on() of the same handler, duplicate on() registration on
the legacy intent spelling, and duplicate on() registration on a
migrated-namespace topic (recognizer_loop:utterance) -- the last pins
the chosen 1-fire behaviour rather than baseline's 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JarbasAl
JarbasAl force-pushed the fix/intent-registration-idempotency branch from afd75ff to 99f0c7f Compare August 14, 2026 13:05
@JarbasAl
JarbasAl marked this pull request as ready for review August 14, 2026 13:10
@JarbasAl
JarbasAl merged commit a65793f into dev Aug 14, 2026
13 checks passed
@JarbasAl
JarbasAl deleted the fix/intent-registration-idempotency branch August 14, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant