fix(plugins): register entry-point plugins declared as module:function - #72094
fix(plugins): register entry-point plugins declared as module:function#72094mannnrachman wants to merge 1 commit into
Conversation
|
Thanks for the focused fix. The premise remains valid on current main: Problems
Suggested changes
This is an automated hermes-sweeper review. |
EntryPoint.load() resolves the module:function form to the referenced attribute (typically the register callable itself), not its module. The loader then looked for a .register attribute on that function object, found none, and warned "Plugin '<name>' has no register() function" on every discovery pass — so pip plugins with module:function entry points never registered at all. Detect a non-module callable from ep.load() and use it directly as the register function, resolving LoadedPlugin.module from sys.modules via the callable's __module__ for attribution. The existing entry-point test masked this because its mocked ep.load() returned the module even though its declared value was module:function; the new regression test mirrors real importlib behavior. Fixes NousResearch#72052 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
221a7b8 to
00633f9
Compare
SummaryOne PR addresses issue #72052. #72094 directly fixes the callable-versus-module mismatch by treating the callable returned for a module:function entry point as the registration function while retaining its owning module for attribution. Related pull requests
Suggested consolidationKeep #72094 open with a salvage path: retain its callable-return loader fix and its assertions for successful registration, absence of a loader error, and owning-module attribution, but rebase and relocate the regression coverage to the current TestPluginDiscovery structure. It is the only PR for #72052, so there are no duplicate PRs to close. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I72052(["issue #72052 (open)"])
P72094["PR #72094 (open)"]
P72094 -->|best fix| I72052
class I72052 open
class P72094 open
class P72094 best
class P72094 target
click I72052 "https://github.com/NousResearch/hermes-agent/issues/72052"
click P72094 "https://github.com/NousResearch/hermes-agent/pull/72094"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 4 kB of PR diffs, 8 kB of issue/PR text, <1 kB of discussion (1 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
What does this PR do?
Makes pip-installed plugins with
module:functionentry points actually register.importlib.metadata.EntryPoint.load()resolves themodule:functionform to the referenced attribute (typically theregistercallable itself), not the module.PluginManager._load_plugin()then looked for a.registerattribute on that function object, found none, and loggedPlugin '<name>' has no register() function— once per discovery pass, which for a gateway with an every-minute cron tick means a warning per minute, forever. Such plugins could never register at all.The loader now detects a non-module callable returned by
ep.load()and uses it directly as the register function.LoadedPlugin.moduleis resolved fromsys.modulesvia the callable's__module__so attribution andhermes plugins listreporting stay intact. Baremoduleentry points and directory plugins are unaffected.Related Issue
Fixes #72052
Type of Change
Changes Made
hermes_cli/plugins.py_load_plugin(): if the entry-point load result is a callable that is not a module, treat it as theregisterfunction directly and resolve the owning module fromsys.modulesforLoadedPlugin.module._load_entrypoint_module(): widen the return annotation and document thatep.load()returns the module for baremoduleentry points, or the referenced attribute for themodule:functionform.tests/hermes_cli/test_plugins.pytest_entry_point_function_form_registerswhose mockedep.load()returns the function — mirroring realimportlibbehavior. The pre-existing entry-point test masked this bug because its mock returned the module even though its declared entry-point value wasmodule:function.How to Test
name = package.module:register(e.g.mnemosyne-hermes), enable it viahermes plugins enable <name>, and confirm it registers instead of warninghas no register() functionon every discovery pass.Validation Results
tests/hermes_cli/test_plugins.py: 117 passed, 0 failed (Python 3.11.15, Ubuntu 22.04 x86_64).mainwith exactly the reported error (AssertionError: no register() function) and passes with the fix.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — focused suite above is green; see the transparency note in Validation ResultsDocumentation & Housekeeping
cli-config.yaml.exampleN/A — no configuration key was added or changedCONTRIBUTING.md/AGENTS.mdN/A — no architecture or contributor workflow changedscripts/check-windows-footguns.py --diff origin/mainpassesScreenshots / Logs
No UI changes. Loader behavior before/after on a
module:functionentry point:🤖 Generated with Claude Code