fix: skip _unbind_plugin for inactivated plugins in reload()#9096
fix: skip _unbind_plugin for inactivated plugins in reload()#9096irmia2026 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request modifies the plugin reload logic in star_manager.py to skip unbinding plugins that are not currently activated. However, the review comments point out critical issues with this approach: during a full reload, skipping the unbind process prevents modules from being cleared from sys.modules, which can cause subsequent loads to fail; during a single plugin reload, it can lead to the plugin's activation state remaining False even after being re-enabled, rendering its event handlers inactive. Detailed code suggestions have been provided to address both of these issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if smd.name and smd.module_path and smd.activated: | ||
| await self._unbind_plugin(smd.name, smd.module_path) |
There was a problem hiding this comment.
在重载所有插件(specified_plugin_name 为 None)的情况下,star_map 和 star_registry 会被完全清空(star_map.clear() 和 star_registry.clear())。
如果在此处对已停用的插件跳过 _unbind_plugin,其对应的模块将不会从 sys.modules 中被清除。当后续调用 load() 时,由于 star_map 已被清空,且模块由于缓存在 sys.modules 中而不会重新执行(导致类装饰器/子类钩子无法再次运行来注册插件),load() 将无法在 star_map 中找到该插件。这会迫使 load() 回退到旧版的加载机制,如果插件类名不符合旧版命名规范(如以 Plugin 结尾或命名为 Main),将会抛出异常导致加载失败。
因此,在全量重载时,不应该跳过 _unbind_plugin。
| if smd.name and smd.module_path and smd.activated: | |
| await self._unbind_plugin(smd.name, smd.module_path) | |
| if smd.name and smd.module_path: | |
| await self._unbind_plugin(smd.name, smd.module_path) |
There was a problem hiding this comment.
Thanks for the review! However, this concern is not applicable here — I've verified it through source analysis.
_unbind_plugin (L1769-1820) never touches sys.modules. It only:
- Deletes from
star_map/star_registry - Removes handlers from
star_handlers_registry - Removes tools from
llm_tools.func_list - Unregisters platform adapters
There is no import importlib, no sys.modules, no reload() call anywhere in the function body.
Additionally, in the full reload path, star_map.clear() and star_registry.clear() are called right after the unbind loop, so any skipped entries are still cleared.
And regarding consistency: _terminate_plugin already has the same guard (if not star_metadata.activated: return) at L1882. Keeping _unbind_plugin aligned with that pattern is the safer design.
| if smd.name and smd.activated: | ||
| await self._unbind_plugin(smd.name, specified_module_path) |
There was a problem hiding this comment.
在单插件重载的情况下,如果该插件之前已被停用,跳过 _unbind_plugin 会导致现有的 StarMetadata 对象被保留并复用。
然而,在 load() 方法中,只有当插件在 inactivated_plugins 中时才会将其 metadata.activated 设为 False,但从未在启用时将其设为 True。这意味着,当用户重新启用该插件(turn_on_plugin)并触发重载时,虽然插件类会被实例化,但其 activated 状态仍将保持为 False。这会导致该插件的所有事件处理器在 get_handlers_by_event_type 中被过滤掉,从而使插件完全无法工作。
建议在跳过 _unbind_plugin 的同时,检查该插件是否已被移出 inactivated_plugins,如果是,则将其 activated 状态恢复为 True。
| if smd.name and smd.activated: | |
| await self._unbind_plugin(smd.name, specified_module_path) | |
| if smd.name and smd.activated: | |
| await self._unbind_plugin(smd.name, specified_module_path) | |
| elif smd.name: | |
| inactivated_plugins = await sp.global_get("inactivated_plugins", []) | |
| if specified_module_path not in inactivated_plugins: | |
| smd.activated = True |
There was a problem hiding this comment.
Good catch on the surface, but the underlying mechanism already handles this correctly. Let me explain:
load() always creates a fresh StarMetadata and overwrites star_map.
At L1294: star_map[path] = metadata — this unconditionally overwrites whatever was in star_map with the newly created metadata object.
At L1299: if metadata.module_path in inactivated_plugins: metadata.activated = False — it only flips to False for inactivated plugins.
The key insight: StarMetadata.activated defaults to True (defined at star.py:51: activated: bool = True). So when load() creates a fresh metadata and the plugin is NOT in inactivated_plugins (i.e., after turn_on_plugin), activated stays True automatically.
Scenario trace:
- Plugin is inactivated →
reload()with our fix skips_unbind_plugin→load()creates fresh metadata →activated = False✅ - User calls
turn_on_plugin→ plugin removed frominactivated_plugins→reload()skips_unbind_plugin→load()creates fresh new metadata →activated = True✅ (not in inactivated_plugins)
The old metadata's activated=False state is irrelevant because star_map[path] = metadata overwrites it every time.
There was a problem hiding this comment.
Gemini Code Assist 评审澄清
感谢评审。两条意见已逐条回复,要点如下:
异议 1(全量 reload 分支 — sys.modules 残留)
_unbind_plugin (L1769-1820) 的实现不涉及 sys.modules/importlib/reload,仅操作 star_map、star_registry、star_handlers_registry、llm_tools.func_list 和平台适配器。全量 reload 路径还会执行 star_map.clear(),彻底清空。此条不成立。
异议 2(单插件 reload 分支 — activated 滞留 False)
load() 在 L1294 执行 star_map[path] = metadata,每次创建新 StarMetadata(默认 activated=True,定义于 star.py:51),随后 L1299 仅在 inactivated_plugins 中时才降为 False。旧 metadata 的状态被覆盖,不存在滞留。此条也不成立。
50c797b to
555fcfa
Compare
Summary / 概述
Fix link: Closes #8582
保存已停用插件的配置时,
reload()无条件调用_unbind_plugin(),导致该插件的全部工具从llm_tools.func_list中永久删除。后续turn_on_plugin()因工具列表为空而无法恢复。本 PR 在
reload()的两处_unbind_plugin()调用前添加smd.activated检查,与_terminate_plugin()已有的停用保护逻辑保持一致。Root Cause / 根因
_terminate_plugin()已有if not star_metadata.activated: return(L1882),但_unbind_plugin缺少同样保护。Changes / 改动
astrbot/core/star/star_manager.py—reload()方法改动:+2 -2,与
_terminate_plugin()逻辑对齐。Verification
测试:
tests/test_plugin_manager.py22/23 通过 | Lint: ruff 0 issuesChecklist
_terminate_plugin()的 activated 检查保持一致Summary by Sourcery
Bug Fixes: