fix: prevent cron/scheduler_provider ModuleNotFoundError from sys.path shadowing - #49414
fix: prevent cron/scheduler_provider ModuleNotFoundError from sys.path shadowing#49414ochsec wants to merge 4 commits into
Conversation
…h shadowing (fixes #49410)
|
Tested successfully on NixOS I tested commit Before the fix:
After the fix:
The module-level import order change resolves the issue reliably. |
|
Closing in favor of #49913, which fixes the root cause (parents[2] -> parents[3] across all 5 migrated platform adapters so they insert the repo root, not plugins/). Your approach pre-imported cron.* at module level to cache sys.modules before the bad sys.path insert — that prevents this specific crash, but the broken parents[2] insert stays in place, so any other module imported after a platform adapter loads remains at risk of the same plugins/ shadowing. The kanban change in this PR was also unrelated. Thanks for digging into this. |
Fixes #49410
Problem
Gateway crashes on startup with
ModuleNotFoundError: No module named 'cron.scheduler_provider'when any platform adapter that insertsplugins/intosys.pathis loaded (discord, raft). The crash creates an infinite restart loop (79+ crash cycles observed).The root cause:
plugins/platforms/discord/adapter.pyandplugins/platforms/raft/adapter.pyboth executesys.path.insert(0, str(_Path(__file__).resolve().parents[2]))at module level, which inserts theplugins/directory atsys.path[0]. Sinceplugins/cron/__init__.pyexists (it's the cron provider discovery module), Python resolvesfrom cron.scheduler_provider import ...toplugins/cron/instead of the realcron/package. Butplugins/cron/is the provider discovery module — it does NOT containscheduler_provider.py, so the import fails.This happens because the
cron.scheduler_providerimports were lazy (insidestart_gateway()and_start_cron_ticker()), executed after the platform adapters had already mutatedsys.path.Root Cause
Import order: gateway code loads → platform adapters insert
plugins/atsys.path[0]→ laterfrom cron.scheduler_provider import ...findsplugins/cron/first →ModuleNotFoundError.Fix
Move the
cron.scheduler_providerandcron.schedulerimports to module level ingateway/run.py, before thefrom gateway.platforms.base import ...line that can transitively trigger adapter loading. This ensures the correctcronpackage is cached insys.modulesbefore anysys.pathmutation occurs.Specific changes:
gateway/run.py: Add module-level imports ofInProcessCronScheduler,resolve_cron_scheduler, and_resolve_home_env_varbeforegateway.platforms.baseimportgateway/run.py: Remove the three now-redundant lazyfrom cron.*imports inside functions (_home_target_env_var,_start_cron_ticker,start_gateway)tests/gateway/test_cron_import_shadowing.py: New test file with three tests verifying the shadowing scenario cannot recurChanges
gateway/run.py— promoted 3 lazy cron imports to module level; added comment explaining why ordering matterstests/gateway/test_cron_import_shadowing.py— new test:test_cron_scheduler_provider_importable_after_plugins_path_insert,test_plugins_cron_does_not_have_scheduler_provider,test_module_level_import_before_platform_baseImpact
This is a minimal, surgical fix. No behavioral change — the same symbols are imported from the same modules, just earlier and deterministically. The module-level imports have no side effects beyond populating
sys.modules. No circular dependency risk:cron.scheduler_provideronly depends onthreading,abc,typing, andhermes_cli.config(lazily insideresolve_cron_scheduler).The same
sys.path.insertpattern exists inplugins/platforms/raft/adapter.py:39, so this fix also prevents the crash when raft is the active adapter. Thegateway/platforms/base.py:491insert is safe (it adds the repo root, notplugins/).