Skip to content

Fix hook namespace swallowing packages with a matching name prefix#88

Merged
jensens merged 1 commit into
mainfrom
fix/hook-namespace-prefix-clash
May 30, 2026
Merged

Fix hook namespace swallowing packages with a matching name prefix#88
jensens merged 1 commit into
mainfrom
fix/hook-namespace-prefix-clash

Conversation

@jensens
Copy link
Copy Markdown
Member

@jensens jensens commented May 30, 2026

Problem

Section ownership in Configuration is decided by an unanchored string prefix match in config.py:

def is_ns_member(name) -> bool:
    for hook in hooks:
        if name.startswith(hook.namespace):   # the bug
            return True
    return False

With the uv hook declaring namespace = "uv", any section whose name merely starts with uv — e.g. uvst.addon, uvxs.addon — is classified as a hook section and dropped from config.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>:":

if name == hook.namespace or name.startswith(f"{hook.namespace}:"):

A colon can never appear in a PyPI/package name, so it is an unambiguous separator between hook sections and package sections. uvst.addon is 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 uv to __uv__. That treats the symptom: it makes a real-world collision unlikely but leaves the broken startswith matching 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 the uv hook 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 no mx.ini sections, so practical impact is nil. EXTENDING.md is 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 on main (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).

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.
Copy link
Copy Markdown

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

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_member in src/mxdev/config.py to require an exact namespace match or a "<namespace>:" prefix.
  • Update EXTENDING.md (and add a CHANGES.md entry) to document the new [namespace:subsection] convention, including the required migration from [namespace-section].
  • Add two regression tests in tests/test_config.py covering 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.

@jensens
Copy link
Copy Markdown
Member Author

jensens commented May 30, 2026

@erral does this solve the problem from #87 ?

@jensens jensens merged commit e493846 into main May 30, 2026
18 checks passed
@jensens jensens deleted the fix/hook-namespace-prefix-clash branch May 30, 2026 13:44
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.

3 participants