[Bug] Plugin loading inserts plugin dirs at sys.path[0] — plugin with api/ dir shadows core api package (500s on lazy handlers)
Title: [Bug] Plugin loading inserts plugin dirs at sys.path[0] — any plugin with an api/ dir shadows the core api package and breaks lazy-loaded API handlers (500s)
Version: v2.12 (also affects v2.11+ plugin loading)
Summary
The new plugin loading (v2.11+) inserts each plugin's directory at the front of sys.path (sys.path[0]). If a plugin ships a directory named api/ (a natural name for a plugin exposing API handlers), it shadows the core api package for every absolute import resolved afterwards. Lazy-loaded core API handlers then crash with ModuleNotFoundError and return HTTP 500.
Reproduction (deterministic)
- Install any plugin whose directory contains
api/(e.g. with an emptyapi/__init__.py— even a namespace-package-shaped empty dir triggers it). - Restart the UI (
run_ui.py). POST /api/message_async(unauthenticated — the failure happens before auth):
ModuleNotFoundError: No module named 'api.message'api/message_async.py line 3: from api.message import Message — resolves api to the plugin's api/ dir (sys.path[0]), which has no message.py.
How I debugged it (method that may help maintainers)
Drop a temporary debug handler api/debug_syspath.py exposing sys.path — it is loaded by the same dispatcher that fails, so it returns the sys.path of the exact failing context:
{"syspath": ["<repo>/usr/plugins/<offending-plugin>", "<repo>", ...], "cwd": "/root", ...}sys.path[0] is the plugin dir → the shadowing is visible directly.
Evidence that it's the plugin loading (not stale state)
import api.messagefrom a shell with cwd=repo: works- Same import inside the running app's dispatcher: fails
- Removing the plugin dir → endpoint returns to healthy (302 to login); re-adding it → 500 again
- Restart does not help (plugin re-inserts its dir at every boot)
- Same handler code (
from api.message import Message) exists in v2.10 and worked there — v2.10's plugin loading did not prepend plugin dirs to sys.path
Impact
Any user plugin containing an api/ directory (a very natural layout for plugins that add API handlers via the documented plugins/<name>/api/<handler>.py convention!) breaks all core API handlers that use absolute api.* imports in the lazy dispatch path. The failure mode is confusing: the tree on disk is intact, imports work from a shell, and only in-process lazy imports fail.
Suggested directions
- Load plugin handlers without mutating
sys.path(the dispatcher already loads by file path viaspec_from_file_location— the sys.path prepend seems only needed for the plugin's own imports; a scoped insertion that's removed after loading would work) - Or: isolate plugin imports (importlib.metadata / per-plugin namespace) so plugin dirs can never shadow core packages
- Or (minimal): reserve
apias a protected top-level name and warn loudly at plugin load when a plugin dir would shadow it
Happy to provide the debug handler file or any extra trace if useful. Thanks for the great project!
Source: agent0ai/agent-zero