Fix hook namespace swallowing packages with a matching name prefix#88
Conversation
Section ownership used an unanchored str.startswith match, so a hook namespace like "uv" silently classified any section whose name merely started with "uv" (e.g. uvst.addon, uvxs.addon) as a hook section and dropped it from the package list - never checked out, never written to requirements, and with no diagnostic. Anchor the match: a section belongs to a hook only when its name is exactly the namespace or is prefixed with "<namespace>:". A colon cannot occur in a package name, so it unambiguously separates hook sections from package sections and removes the false-positive class permanently. This is an alternative to #87, which worked around the bug by renaming the uv hook's namespace to "__uv__". Fixing the matching logic instead keeps the clean "uv" namespace and protects every current and future hook, not just this one. Hook config sections previously named [namespace-section] must be renamed to [namespace:section]; EXTENDING.md updated accordingly. The only known hook (uv) owns no mx.ini sections, so the practical impact is nil.
There was a problem hiding this comment.
Pull request overview
Fixes a silent bug in Configuration where hook section ownership used an unanchored str.startswith match, causing any package section whose name began with a hook namespace (e.g. uvst.addon with the built-in uv hook) to be misclassified as a hook section and dropped from config.packages. The fix anchors the match: a section belongs to a hook only when its name equals the namespace exactly or is prefixed with "<namespace>:". This presents an alternative to PR #87 (which avoided the collision by renaming the namespace to __uv__).
Changes:
- Tighten
is_ns_memberinsrc/mxdev/config.pyto require an exact namespace match or a"<namespace>:"prefix. - Update
EXTENDING.md(and add aCHANGES.mdentry) to document the new[namespace:subsection]convention, including the required migration from[namespace-section]. - Add two regression tests in
tests/test_config.pycovering the swallowed-package case and the still-routed[namespace]/[namespace:sub]cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/mxdev/config.py | Anchors hook namespace matching to exact name or namespace: prefix; adds explanatory comment. |
| tests/test_config.py | Adds regression test for non-swallowing and a positive test for [namespace] / [namespace:sub] routing. |
| EXTENDING.md | Documents new : delimiter convention and updates the example hook code accordingly. |
| CHANGES.md | Adds changelog entry describing the fix and the section-rename migration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Problem
Section ownership in
Configurationis decided by an unanchored string prefix match inconfig.py:With the
uvhook declaringnamespace = "uv", any section whose name merely starts withuv— e.g.uvst.addon,uvxs.addon— is classified as a hook section and dropped fromconfig.packages. The package is never checked out, never written to requirements, and there is no error or warning. It just silently vanishes.This is the issue reported in #87.
Fix
Anchor the match. A section belongs to a hook only when its name is exactly the namespace or is prefixed with
"<namespace>:":A colon can never appear in a PyPI/package name, so it is an unambiguous separator between hook sections and package sections.
uvst.addonis now correctly a package;[uv]and[uv:sources]remain hook sections.Relationship to #87
#87 fixes the same report by renaming the hook namespace from
uvto__uv__. That treats the symptom: it makes a real-world collision unlikely but leaves the brokenstartswithmatching in place, leaks an__uv__sentinel into user-facing INI, and leaves every other (current and future) hook exposed to the same trap. This PR fixes the matching logic itself, so theuvhook can keep its clean namespace and all hooks are protected. I'd suggest this supersedes #87.Migration
Hook config sections previously named
[namespace-section]must be renamed to[namespace:section]. The only known hook (uv) owns nomx.inisections, so practical impact is nil.EXTENDING.mdis updated to document the:convention. Namespaced settings keys in[settings](namespace-key) are unchanged.Tests (TDD)
test_package_name_starting_with_hook_namespace_is_not_swallowed— the regression; fails onmain(package dropped), passes with the fix.test_hook_section_with_namespace_delimiter_belongs_to_hook— guards that[namespace]/[namespace:sub]are still routed to hooks.Full suite: 212 passed, 4 skipped. Lint clean (ruff, isort, mypy).