Skip to content

fix: close messagebus client before main() returns on shutdown - #870

Draft
JarbasAl wants to merge 2 commits into
devfrom
fix/shutdown-executor-race
Draft

fix: close messagebus client before main() returns on shutdown#870
JarbasAl wants to merge 2 commits into
devfrom
fix/shutdown-executor-race

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Aug 13, 2026

Copy link
Copy Markdown
Member

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

This addresses the shutdown hang seen on systemctl restart of ovos-core, where the service sits in deactivating (stop-sigterm) for 60+ seconds until systemd sends SIGKILL, with RuntimeError: cannot schedule new futures after shutdown in the logs. The MessageBusClient used by the skills service is never closed anywhere in ovos_core/__main__.py. bus.run_in_thread() starts a daemon thread that keeps receiving messages and dispatching them through bus.emitter, which is a pyee.ExecutorEventEmitter backed by a ThreadPoolExecutor. That thread is still running when main() returns and the interpreter starts tearing down, so a message arriving during that window can still call emitter.emit() -> executor.submit() after the executor is gone, which is exactly the error text seen on the box.

Sibling OVOS services (ovos-dinkum-listener, mycroft-classic-listener, ovos-workshop's OVOSAbstractApplication) all call bus.close() as part of their shutdown sequence; ovos-core's skills service was the one place that skipped it. The fix adds bus.close() right after skill_manager.shutdown() in main(), and now also captures the thread returned by bus.run_in_thread() and joins it with a bounded timeout. bus.close() alone is fire-and-forget: it signals the run_forever loop to stop and closes the socket, but does not wait for the receiver thread to exit. The join narrows the residual race further but is not a synchronous guarantee — it can itself time out, so this reduces rather than eliminates the window where a buffered inbound frame is dispatched during teardown.

I added a regression test in test/unittests/test_main.py that mocks the bus (and the thread object it returns) and asserts bus.close() and bus_thread.join() (with a timeout) are both called before main() returns. I checked the join assertion fails against the code before this change and passes after.

Full local test/unittests suite: 424 passed, 6 xfailed, no new failures, on a throwaway uv venv (--prerelease=allow) against ovos-core dev plus latest prerelease deps.

skill_manager.shutdown() never closed the MessageBusClient, so the
daemon thread started by bus.run_in_thread() stayed alive after
main() returned. Any message it received during interpreter teardown
could still call emitter.emit() -> executor.submit() on the bus
client's pyee ExecutorEventEmitter after that executor was torn down,
raising "cannot schedule new futures after shutdown" and leaving the
process unable to exit cleanly on SIGTERM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

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: 5bc26aa6-4b2a-4398-b66c-6f45883bd89c

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 13, 2026

Copy link
Copy Markdown

The bots have finished their work. Take a look! 🤖

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

📋 Repo Health

I've performed a holistic audit of your project's soul. 🧘

✅ All required files present.

Latest Version: 3.0.2a1

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

🔎 Type Check

Processing complete! Details follow. 📬

mypy: 300 error(s) found

ovos_core/main.py:24:1: error: Skipping analyzing "ovos_utils.log": module is installed, but missing library stubs or py.typed marker [import-untyped]

Errors (showing first 10/300)
test/unittests/test_skill/__init__.py:15:1: error: Skipping analyzing "ovos_workshop.skills.ovos": module is installed, but missing library stubs or py.typed marker  [import-untyped]
test/end2end/conftest.py:10:1: error: Cannot find implementation or library stub for module named "pytest"  [import-not-found]
ovos_core/transformers.py:3:1: error: Skipping analyzing "ovos_config": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:4:1: error: Skipping analyzing "ovos_plugin_manager.intent_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:5:1: error: Skipping analyzing "ovos_plugin_manager.metadata_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:6:1: error: Skipping analyzing "ovos_plugin_manager.text_transformers": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/transformers.py:7:1: error: Skipping analyzing "ovos_plugin_manager.transformer_services": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:17:1: error: Skipping analyzing "ovos_bus_client.message": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:18:1: error: Skipping analyzing "ovos_spec_tools": module is installed, but missing library stubs or py.typed marker  [import-untyped]
ovos_core/intent_services/manifest.py:19:1: error: Skipping analyzing "ovos_utils.log": module is installed, but missing library stubs or py.typed marker  [import-untyped]

📚 Docs

A quick update on the status of your PR. 🔔

✅ All required documentation files present.

README.md

🔒 Security (pip-audit)

Shields up! Scanning for potential threats. 🛡️

✅ No known vulnerabilities found (113 packages scanned).

📊 Coverage

How deep does the testing rabbit hole go? 🐇

⚠️ 71.7% total coverage

Files below 80% coverage (8 files)
File Coverage Missing lines
ovos_core/__init__.py 0.0% 7
ovos_core/intent_services/__init__.py 0.0% 1
ovos_core/transformers.py 33.3% 18
ovos_core/skill_installer.py 45.3% 134
ovos_core/skill_manager.py 62.0% 157
ovos_core/__main__.py 69.0% 9
ovos_core/intent_services/service.py 70.9% 146
ovos_core/intent_services/dispatcher.py 75.7% 27

Full report: download the coverage-report artifact.

🔌 Plugin Detection

Is this plugin ready for its debut in the Manager? 🎭

Plugin Status: ERRORS (1)

Plugin Info:

  • Name: ovos-core
  • Description: The spiritual successor to Mycroft AI, OVOS is flexible voice assistant software that can be run almost anywhere!

OPM Detection:

Plugin Type Wheel Editable
pipeline

Entry Point Validation:

Entry Point Type Import Interface
ovos-converse-pipeline-plugin pipeline ✅ 1496ms
ovos-fallback-pipeline-plugin pipeline ✅ 2ms
ovos-stop-pipeline-plugin pipeline ✅ 58ms

⊘ No settingsmeta.json
requires-python >=3.10 — running Python 3.11

Issues:

  • ❌ Import time for ovos-converse-pipeline-plugin exceeds 500ms (1496ms)
  • ⚠️ No settingsmeta.json found
  • ⚠️ No settingsmeta.json found

🔌 Skill Tests (ovoscope)

I've put the skill through its paces with live intent matching. 🏃

13/37 passed

❌ **TestAdaptIntent** — 0/4
Test Result
test_intent_blacklist ❌ subtests passed
test_adapt_match ❌ subtests passed
test_padatious_no_match ❌ subtests passed
test_skill_blacklist ❌ subtests passed

test_intent_blacklist failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_adapt_match failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_padatious_no_match failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestCancelIntentMidSentence** — 0/1
Test Result
test_cancel_match ❌ subtests passed

test_cancel_match failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestContext1EndToEndReachability** — 3/3
❌ **TestConverse** — 0/1
Test Result
test_parrot_mode ❌ subtests passed

test_parrot_mode failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestDeactivate** — 2/3
Test Result
test_activate ✅ passed
test_deactivate ✅ passed
test_deactivate_inside_converse ❌ subtests passed

test_deactivate_inside_converse failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestFallback** — 0/1
Test Result
test_fallback_match ❌ subtests passed

test_fallback_match failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestGGWaveSkillInstall** — 2/2 ✅ **TestGlobalStopSpec** — 1/1
❌ **TestIntentPipelineRouting** — 0/4
Test Result
test_padatious_intent_matched ❌ subtests passed
test_high_priority_stage_handles_before_low ❌ subtests passed
test_no_match_produces_intent_failure ❌ subtests passed
test_blacklisted_skill_falls_through_to_failure ❌ subtests passed

test_padatious_intent_matched failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_high_priority_stage_handles_before_low failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_no_match_produces_intent_failure failure:

[gw0] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestLangDisambiguation** — 0/4
Test Result
test_lang_text_detection ❌ subtests passed
test_metadata_preferred_over_text_detection ❌ subtests passed
test_invalid_lang_detection ❌ subtests passed
test_stt_lang ❌ subtests passed

test_lang_text_detection failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_metadata_preferred_over_text_detection failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_invalid_lang_detection failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestLegacyGlobalStop** — 1/1
❌ **TestLegacyIntentIdBackCompat** — 0/2
Test Result
test_legacy_blacklist_id_suppresses ❌ subtests passed
test_legacy_dispatch_topic_fires_handler ❌ subtests passed

test_legacy_blacklist_id_suppresses failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_legacy_dispatch_topic_fires_handler failure:

[gw2] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestLegacyTargetedStop** — 1/1
❌ **TestNoSkills** — 0/2
Test Result
test_routing ❌ subtests passed
test_complete_failure ❌ subtests passed

test_routing failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_complete_failure failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
❌ **TestPadatiousIntent** — 0/4
Test Result
test_padatious_match ❌ subtests passed
test_skill_blacklist ❌ subtests passed
test_intent_blacklist ❌ subtests passed
test_adapt_no_match ❌ subtests passed

test_padatious_match failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_skill_blacklist failure:

[gw1] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python

test_intent_blacklist failure:

[gw3] linux -- Python 3.11.15 /opt/hostedtoolcache/Python/3.11.15/x64/bin/python
✅ **TestResponseModeHolderStopE2E** — 2/2 ✅ **TestTargetedStopSpec** — 1/1

🚌 Bus Coverage

Checking the insulation of our event handlers. 🏠

🔴 Coverage Summary

Metric Status Coverage
Listeners █░░░░░░░░░ 11.9% 28/236 handlers
Emitters ██████████ 100% 46/46 observed
Assertions ██████████ 100% 46/46 asserted

📊 Per-Skill Breakdown

Skill Listeners Observed Asserted
AdaptPipeline 0/14 (0.0%) 0/0 0/0
ConverseService 2/4 (50.0%) 0/0 0/0
DomainAdaptPipeline 0/14 (0.0%) 0/0 0/0
FallbackService 2/4 (50.0%) 0/0 0/0
HierarchicalAdaptPipeline 0/14 (0.0%) 0/0 0/0
IntentDispatcher 1/2 (50.0%) 0/0 0/0
IntentManifest 0/8 (0.0%) 0/0 0/0
IntentService 2/4 (50.0%) 0/0 0/0
Model2VecPrototypePipeline 0/12 (0.0%) 0/0 0/0
PadaciosoPipeline 0/11 (0.0%) 0/0 0/0
PadatiousPipeline 0/15 (0.0%) 0/0 0/0
SkillManager 0/5 (0.0%) 0/0 0/0
_LegacyStopBridge 2/3 (66.7%) 0/0 0/0
__core__ 7/30 (23.3%) 14/14 14/14
ovos-skill-count.openvoiceos 2/20 (10.0%) 0/0 0/0
ovos-skill-fallback-unknown.openvoiceos 2/21 (9.5%) 4/4 4/4
ovos-skill-hello-world.openvoiceos 1/24 (4.2%) 9/9 9/9
stop.openvoiceos 0/0 (0.0%) 5/5 5/5
test_activation.openvoiceos 5/27 (18.5%) 14/14 14/14
type 2/4 (50.0%) 0/0 0/0
🔍 Detailed Message Type Breakdown

AdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

ConverseService

⚠️ Uncovered Listeners:

  • intent.service.active_skills.get
  • intent.service.skills.activate
    ✅ Covered Listeners:
  • converse:skill (2x)
  • intent.service.skills.deactivate (2x)

DomainAdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

FallbackService

⚠️ Uncovered Listeners:

  • ovos.skills.fallback.deregister
  • ovos.skills.fallback.register
    ✅ Covered Listeners:
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.response (2x)
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.start (2x)

HierarchicalAdaptPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.keyword (Intent)
  • detach_intent
  • detach_skill
  • intent.service.adapt.get
  • intent.service.adapt.manifest.get
  • intent.service.adapt.vocab.manifest.get
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent
  • register_vocab

IntentDispatcher

⚠️ Uncovered Listeners:

  • mycroft.skill.handler.error
    ✅ Covered Listeners:
  • mycroft.skill.handler.complete (9x)

IntentManifest

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.describe (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.list (Intent)
  • ovos.intent.register.keyword (Intent)
  • ovos.intent.register.template (Intent)
  • ovos.skill.deregister

IntentService

⚠️ Uncovered Listeners:

  • intent.service.intent.get (Intent)
  • intent.service.pipelines.reload
    ✅ Covered Listeners:
  • intent.service.skills.deactivate (2x)
  • ovos.utterance.handle (13x)

Model2VecPrototypePipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • mycroft.ready
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister
  • register_intent

PadaciosoPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_entity (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister

PadatiousPipeline

⚠️ Uncovered Listeners:

  • ovos.intent.deregister (Intent)
  • ovos.intent.disable (Intent)
  • ovos.intent.enable (Intent)
  • ovos.intent.register.template (Intent)
  • padatious:register_entity (Intent)
  • padatious:register_intent (Intent)
  • detach_intent
  • detach_skill
  • intent.service.padatious.entities.manifest.get
  • intent.service.padatious.get
  • intent.service.padatious.manifest.get
  • mycroft.skills.train
  • ovos.entity.deregister
  • ovos.entity.register
  • ovos.skill.deregister

SkillManager

⚠️ Uncovered Listeners:

  • skill.converse.get_response.disable
  • skillmanager.activate
  • skillmanager.deactivate
  • skillmanager.keep
  • skillmanager.list

_LegacyStopBridge

⚠️ Uncovered Listeners:

  • stop:global (Intent)
    ✅ Covered Listeners:
  • ovos.intent.matched (7x)
  • stop:skill (1x)

__core__

⚠️ Uncovered Listeners:

  • ovos-stop-pipeline-plugin:global_stop (Intent)
  • add_context
  • clear_context
  • message
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.all_loaded
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.is_alive
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.is_ready
  • mycroft.ovos-skill-hello-world.openvoiceos.all_loaded
  • mycroft.ovos-skill-hello-world.openvoiceos.is_alive
  • mycroft.ovos-skill-hello-world.openvoiceos.is_ready
  • mycroft.test_activation.openvoiceos.all_loaded
  • mycroft.test_activation.openvoiceos.is_alive
  • mycroft.test_activation.openvoiceos.is_ready
  • ovos-skill-fallback-unknown.openvoiceos.set
  • ovos-skill-hello-world.openvoiceos.set
  • ovos.session.sync
  • ovos.skills.fallback.force_timeout
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.response
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.start
  • remove_context
  • skill.converse.get_response.enable
  • test_activation.openvoiceos.set
    ✅ Covered Listeners:
  • ovos.session.update_default (4x)
  • ovos.skills.converse.force_timeout (1x)
  • ovos.skills.fallback.pong (1x)
  • ovos.utterance.handled (25x)
  • ovos.utterance.speak (4x)
  • skill.converse.pong (1x)
  • skill.converse.response (1x)

📤 Emitters:

  • mycroft.audio.play_sound (Asserted ✅)
  • ovos.intent.handler.complete (Asserted ✅)
  • ovos.intent.handler.start (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.intent.unmatched (Asserted ✅)
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.request (Asserted ✅)
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.response (Asserted ✅)
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.start (Asserted ✅)
  • ovos.skills.fallback.ping (Asserted ✅)
  • ovos.utterance.cancelled (Asserted ✅)
  • ovos.utterance.handle (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • recognizer_loop:utterance (Asserted ✅)
  • test_activation.openvoiceos.converse.ping (Asserted ✅)

ovos-skill-count.openvoiceos

⚠️ Uncovered Listeners:

  • ovos-skill-count.openvoiceos:count_to_n (Intent)
  • question:action (Intent)
  • question:action.ovos-skill-count.openvoiceos (Intent)
  • question:query (Intent)
  • homescreen.metadata.get
  • mycroft.ovos-skill-count.openvoiceos.all_loaded
  • mycroft.ovos-skill-count.openvoiceos.is_alive
  • mycroft.ovos-skill-count.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-count.openvoiceos.converse.get_response
  • ovos-skill-count.openvoiceos.set
  • ovos.common_query.ping
  • ovos.skills.settings_changed
    ✅ Covered Listeners:
  • ovos-skill-count.openvoiceos.stop (1x)
  • ovos-skill-count.openvoiceos.stop.ping (1x)

ovos-skill-fallback-unknown.openvoiceos

⚠️ Uncovered Listeners:

  • question:action (Intent)
  • question:action.ovos-skill-fallback-unknown.openvoiceos (Intent)
  • question:query (Intent)
  • homescreen.metadata.get
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.all_loaded
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.is_alive
  • mycroft.ovos-skill-fallback-unknown.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-fallback-unknown.openvoiceos.converse.get_response
  • ovos-skill-fallback-unknown.openvoiceos.set
  • ovos-skill-fallback-unknown.openvoiceos.stop
  • ovos-skill-fallback-unknown.openvoiceos.stop.ping
  • ovos.common_query.ping
  • ovos.skills.settings_changed
    ✅ Covered Listeners:
  • ovos.skills.fallback.ovos-skill-fallback-unknown.openvoiceos.request (2x)
  • ovos.skills.fallback.ping (2x)

📤 Emitters:

  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos.skills.fallback.pong (Asserted ✅)
  • ovos.utterance.speak (Asserted ✅)

ovos-skill-hello-world.openvoiceos

⚠️ Uncovered Listeners:

  • ovos-skill-hello-world.openvoiceos:HelloWorldIntent (Intent)
  • ovos-skill-hello-world.openvoiceos:HowAreYou (Intent)
  • ovos-skill-hello-world.openvoiceos:ThankYouIntent (Intent)
  • question:action (Intent)
  • question:action.ovos-skill-hello-world.openvoiceos (Intent)
  • question:query (Intent)
  • hello.world
  • homescreen.metadata.get
  • mycroft.ovos-skill-hello-world.openvoiceos.all_loaded
  • mycroft.ovos-skill-hello-world.openvoiceos.is_alive
  • mycroft.ovos-skill-hello-world.openvoiceos.is_ready
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • ovos-skill-hello-world.openvoiceos.converse.get_response
  • ovos-skill-hello-world.openvoiceos.set
  • ovos-skill-hello-world.openvoiceos.stop
  • ovos-skill-hello-world.openvoiceos.stop.ping
  • ovos.common_query.ping
  • ovos.skills.settings_changed
    ✅ Covered Listeners:
  • ovos-skill-hello-world.openvoiceos:Greetings (2x)

📤 Emitters:

  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos-skill-hello-world.openvoiceos.activate (Asserted ✅)
  • ovos-skill-hello-world.openvoiceos:Greetings (Asserted ✅)
  • ovos.intent.handler.complete (Asserted ✅)
  • ovos.intent.handler.start (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • ovos.utterance.speak (Asserted ✅)

stop.openvoiceos

📤 Emitters:

  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos-skill-count.openvoiceos.stop (Asserted ✅)
  • stop.openvoiceos.activate (Asserted ✅)
  • stop:skill (Asserted ✅)

test_activation.openvoiceos

⚠️ Uncovered Listeners:

  • question:action (Intent)
  • question:action.test_activation.openvoiceos (Intent)
  • question:query (Intent)
  • homescreen.metadata.get
  • intent.service.skills.activated
  • mycroft.skill.disable_intent
  • mycroft.skill.enable_intent
  • mycroft.skill.remove_cross_context
  • mycroft.skill.set_cross_context
  • mycroft.skills.settings.changed
  • mycroft.stop
  • mycroft.test_activation.openvoiceos.all_loaded
  • mycroft.test_activation.openvoiceos.is_alive
  • mycroft.test_activation.openvoiceos.is_ready
  • ovos.common_query.ping
  • ovos.skills.settings_changed
  • test_activate
  • test_activation.openvoiceos.converse.get_response
  • test_activation.openvoiceos.set
  • test_activation.openvoiceos.stop
  • test_activation.openvoiceos.stop.ping
  • test_deactivate
    ✅ Covered Listeners:
  • intent.service.skills.deactivated (2x)
  • test_activation.openvoiceos.activate (2x)
  • test_activation.openvoiceos.converse.ping (2x)
  • test_activation.openvoiceos.converse.request (2x)
  • test_activation.openvoiceos.deactivate (2x)

📤 Emitters:

  • converse:skill (Asserted ✅)
  • intent.service.skills.deactivate (Asserted ✅)
  • intent.service.skills.deactivated (Asserted ✅)
  • mycroft.skill.handler.complete (Asserted ✅)
  • mycroft.skill.handler.start (Asserted ✅)
  • ovos.intent.handler.complete (Asserted ✅)
  • ovos.intent.handler.start (Asserted ✅)
  • ovos.intent.matched (Asserted ✅)
  • ovos.utterance.handled (Asserted ✅)
  • skill.converse.pong (Asserted ✅)
  • skill.converse.response (Asserted ✅)
  • test_activation.openvoiceos.activate (Asserted ✅)
  • test_activation.openvoiceos.converse.request (Asserted ✅)
  • test_activation.openvoiceos.deactivate (Asserted ✅)

type

⚠️ Uncovered Listeners:

  • recognizer_loop:record_begin (Intent)
  • recognizer_loop:record_end (Intent)
    ✅ Covered Listeners:
  • recognizer_loop:audio_output_end (1x)
  • recognizer_loop:audio_output_start (4x)

🔨 Build Tests

Construction of your features is officially finished. 🏠

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

⚖️ License Check

Ensuring our licenses are consistent and clear. 📄

✅ No license violations found.

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

🌍 Locale Build

Another check completed successfully! 🏁

✅ Locale properly configured (64 files, 17 languages)

Locale directories found:

  • ovos_core/intent_services/locale

Localization coverage:

  • ovos_core/intent_services/locale: 64 files in 17 languages (en-us, ca-es, fa-ir, de-de, gl-es...)

pyproject.toml:[tool.setuptools.package-data.ovos_core] includes locale

  • intent_services/locale/*/*.voc

Build manifest: ✅ 31 locale files included in package


Processing... Done! Have a productive day! ☕

bus.close() is fire-and-forget: it signals run_forever to stop and
closes the socket, but does not wait for the receiver thread spawned by
bus.run_in_thread() to actually exit. Capture that thread and join it
with a bounded timeout after close(), narrowing (not eliminating) the
window where a buffered inbound frame can still hit the emitter's
executor during interpreter teardown. Soften the code comment and test
docstring accordingly: this is not a synchronous guarantee, since the
join can time out.

Verified ovos_bus_client.MessageBusClient.run_in_thread() returns the
Thread object in the installed version. Updated the regression test to
mock the returned thread and assert join() is called with a timeout,
alongside the existing close() assertion; confirmed it fails without
the join and passes with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added fix and removed fix labels Aug 13, 2026
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