#1897·agent-zero

[Bug] Plugin loading inserts plugin dirs at sys.path[0] — plugin with api/ dir shadows core api package (500s on lazy handlers)

Author: pabloalgoCreated Sep 11, 2026Updated Sep 11, 2026

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)

  1. Install any plugin whose directory contains api/ (e.g. with an empty api/__init__.py — even a namespace-package-shaped empty dir triggers it).
  2. Restart the UI (run_ui.py).
  3. 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:

json
{"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.message from 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 via spec_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 api as 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!