Skip to content

fix: detach_skill removes intents by skill_id prefix, not substring - #102

Draft
JarbasAl wants to merge 1 commit into
devfrom
fix/detach-skill-prefix-match
Draft

fix: detach_skill removes intents by skill_id prefix, not substring#102
JarbasAl wants to merge 1 commit into
devfrom
fix/detach-skill-prefix-match

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Sep 8, 2026

Copy link
Copy Markdown
Member

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

handle_detach_skill in padacioso/opm.py picked the intents to remove with skill_id in i, a substring test over the whole <skill_id>:<name> intent name. When a skill with id me detaches, skill-a.me:one and skill-b.me:bone go with it; a detach_skill for skill clears both victim.skill:on and otherskill:on. The entity branch of the same handler, and adapt and padatious, already match on the <skill_id>: prefix.

The intent selection now uses startswith(skill_id + ":"). The colon is the intent-name namespace separator (OVOS-MSG-1 §2.1.1), so the owner of a.b:c is exactly a.b. The handler's skill_id resolution is untouched; this PR is independent of #101 and does not overlap its lines.

test/test_detach_skill_prefix.py asserts the roster values for both scenarios above. With the source change reverted both tests fail (registered_intents ends up [] instead of the two surviving intents); with it applied both pass. Full suite: 90 passed, 2 skipped (the two ovoscope e2e modules, skipped identically on dev because ovoscope is not installed in the test venv).

Verified against source by the authoring model only: the defect line on origin/dev, the fail-before flip, and the suite counts. No human has reviewed this change.

handle_detach_skill selected intents to remove with `skill_id in i`, a
substring test over the full "<skill_id>:<name>" intent name. A skill
whose id is a substring of another skill's id (e.g. "me" against
"skill-a.me") wiped the other skill's intents when it detached, and an
unregistered id like "skill" cleared every intent containing that text.
The entity branch of the same handler already matched on the
"<skill_id>:" prefix, as do adapt and padatious.

Intents are now selected with startswith(skill_id + ":"), the colon being
the intent-name namespace separator (OVOS-MSG-1 §2.1.1), so the owner of
"a.b:c" is exactly "a.b".

Regression test test/test_detach_skill_prefix.py: 2 failed before the fix
(registered_intents emptied to [] in both cases), 2 pass after. Full
suite: 90 passed, 2 skipped.

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

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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 Sep 8, 2026

Copy link
Copy Markdown

Hello! The automated checks have been performed. 👋

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

📋 Repo Health

Checking the repo's cholesterol levels (aka code bloat). 🥩

✅ All required files present.

Latest Version: 2.3.4a1

padacioso/version.py — Version file
README.md — README
LICENSE.md — License file (consider renaming to LICENSE)
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
padacioso/version.py has valid version block markers

🔌 Plugin Detection

I've verified the plugin's 'security' signature. ✍️

Plugin Status: ERRORS (1)

Plugin Info:

  • Name: padacioso
  • Description: dead simple intent parser

OPM Detection:

Plugin Type Wheel Editable
pipeline

Entry Point Validation:

Entry Point Type Import Interface
ovos-padacioso-pipeline-plugin pipeline ✅ 765ms

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

Issues:

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

🔌 Skill Tests (ovoscope)

Checking the skill's 'poker face' during errors. 😐

8/20 passed, 12 failed

❌ **TestDetach** — 0/2
Test Result
test_detach_skill_removes_all_its_intents ❌ failed
test_detach_intent_prevents_match ❌ failed

test_detach_skill_removes_all_its_intents failure:

thon/3.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestDetach testMethod=test_detach_skill_removes_all_its_intents>

    def test_detach_skill_removes_all_its_intents(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestDetach testMethod=test_detach_skill_removes_all_its_intents>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_detach_intent_prevents_match failure:

/opt/hostedtoolcache/Python/3.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestDetach testMethod=test_detach_intent_prevents_match>

    def test_detach_intent_prevents_match(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:84: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestDetach testMethod=test_detach_intent_prevents_match>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError
❌ **TestEntityExtraction** — 0/1
Test Result
test_entity_slot_captured_in_match ❌ failed

test_entity_slot_captured_in_match failure:

hon 3.11.16 /opt/hostedtoolcache/Python/3.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestEntityExtraction testMethod=test_entity_slot_captured_in_match>

    def test_entity_slot_captured_in_match(self):
>       self._register_entity("item", ["milk", "bread", "eggs", "cheese"])

test/test_ovoscope_e2e.py:72: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestEntityExtraction testMethod=test_entity_slot_captured_in_match>
name = 'item', samples = ['milk', 'bread', 'eggs', 'cheese']

    def _register_entity(self, name, samples):
>       register_padatious_entity(self.bus, name, samples)
E       TypeError: register_padatious_entity() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:44: TypeError
❌ **TestLegacyStillConsumed** — 0/1
Test Result
test_legacy_template_registration_still_matches ❌ failed

test_legacy_template_registration_still_matches failure:

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

self = <test_intent4_consume_e2e.TestLegacyStillConsumed testMethod=test_legacy_template_registration_still_matches>

    def test_legacy_template_registration_still_matches(self):
        from ovoscope import register_padatious_intent
>       register_padatious_intent(self.bus, f"{self.SKILL_ID}:bye", _BYE)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/end2end/test_intent4_consume_e2e.py:108: TypeError
✅ **TestNegativeKeywordTopic** — 1/1
❌ **TestRegisteredIntentMatch** — 1/4
Test Result
test_best_intent_selected_among_multiple ❌ failed
test_exact_utterance_dispatches_intent ❌ failed
test_no_match_unrelated_utterance ❌ failed
test_no_match_when_no_intents_registered ✅ passed

test_best_intent_selected_among_multiple failure:

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_best_intent_selected_among_multiple>

    def test_best_intent_selected_among_multiple(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:63: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_best_intent_selected_among_multiple>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_exact_utterance_dispatches_intent failure:

python

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_exact_utterance_dispatches_intent>

    def test_exact_utterance_dispatches_intent(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:49: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_exact_utterance_dispatches_intent>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_no_match_unrelated_utterance failure:

.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_no_match_unrelated_utterance>

    def test_no_match_unrelated_utterance(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:59: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestRegisteredIntentMatch testMethod=test_no_match_unrelated_utterance>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError
❌ **TestSessionBlacklist** — 0/2
Test Result
test_blacklisted_intent_is_skipped ❌ failed
test_blacklisted_skill_is_skipped ❌ failed

test_blacklisted_intent_is_skipped failure:

thon/3.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestSessionBlacklist testMethod=test_blacklisted_intent_is_skipped>

    def test_blacklisted_intent_is_skipped(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:109: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestSessionBlacklist testMethod=test_blacklisted_intent_is_skipped>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_blacklisted_skill_is_skipped failure:

/Python/3.11.16/x64/bin/python

self = <test_ovoscope_e2e.TestSessionBlacklist testMethod=test_blacklisted_skill_is_skipped>

    def test_blacklisted_skill_is_skipped(self):
>       self._register_intent(f"{self.SKILL_ID}:hello", _HELLO_SAMPLES)

test/test_ovoscope_e2e.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestSessionBlacklist testMethod=test_blacklisted_skill_is_skipped>
name = 'test_skill_padacioso:hello'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError
❌ **TestSessionBlacklistAlias** — 0/3
Test Result
test_blacklisting_legacy_id_suppresses_new_alias ❌ failed
test_blacklisting_new_id_suppresses_legacy_alias ❌ failed
test_non_blacklisted_intent_still_matches ❌ failed

test_blacklisting_legacy_id_suppresses_new_alias failure:

gister_both_aliases()

test/test_ovoscope_e2e.py:146: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
test/test_ovoscope_e2e.py:142: in _register_both_aliases
    self._register_intent(self.LEGACY_NAME, _HELLO_SAMPLES)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestSessionBlacklistAlias testMethod=test_blacklisting_legacy_id_suppresses_new_alias>
name = 'test_skill_padacioso:hello.intent'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_blacklisting_new_id_suppresses_legacy_alias failure:

gister_both_aliases()

test/test_ovoscope_e2e.py:154: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
test/test_ovoscope_e2e.py:142: in _register_both_aliases
    self._register_intent(self.LEGACY_NAME, _HELLO_SAMPLES)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestSessionBlacklistAlias testMethod=test_blacklisting_new_id_suppresses_legacy_alias>
name = 'test_skill_padacioso:hello.intent'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError

test_non_blacklisted_intent_still_matches failure:

elf._register_both_aliases()

test/test_ovoscope_e2e.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
test/test_ovoscope_e2e.py:142: in _register_both_aliases
    self._register_intent(self.LEGACY_NAME, _HELLO_SAMPLES)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <test_ovoscope_e2e.TestSessionBlacklistAlias testMethod=test_non_blacklisted_intent_still_matches>
name = 'test_skill_padacioso:hello.intent'
samples = ['hello', 'hi', 'hey', 'greetings', 'good morning']

    def _register_intent(self, name, samples):
>       register_padatious_intent(self.bus, name, samples)
E       TypeError: register_padatious_intent() missing 1 required keyword-only argument: 'skill_id'

test/test_ovoscope_e2e.py:41: TypeError
✅ **TestSpecDeregister** — 2/2 ✅ **TestSpecDisableEnable** — 2/2 ✅ **TestSpecTemplateConsumed** — 2/2

🚌 Bus Coverage

Ensuring the bus logic is battle-tested. ⚔️

⚠️ Bus coverage report unavailable — check the job log.

🏷️ Release Preview

Evaluating the overall quality of the next release. ✨

Current: 2.3.4a1Next: 2.3.5a1

Signal Value
Label (none)
PR title fix: detach_skill removes intents by skill_id prefix, not substring
Bump build

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 2.3.5a1

Channel Status Note Current Constraint
Stable Not in channel -
Testing Not in channel -
Alpha Not in channel -

🔒 Security (pip-audit)

Scanning for any potential SQL injection points. 💉

✅ No known vulnerabilities found (49 packages scanned).

🔍 Lint

Here's the latest update on this check. 🗞️

ruff: issues found — see job log

📊 Coverage

Ensuring the logic is battle-tested. ⚔️

82.0% total coverage

Per-file coverage (3 files)
File Coverage Missing lines
padacioso/version.py 0.0% 5
padacioso/opm.py 82.4% 72
padacioso/__init__.py 82.7% 61

Full report: download the coverage-report artifact.

🔨 Build Tests

I've laid the bricks for your new logic. 🧱

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

Built by scripts, maintained by community 🤝

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