{"record":{"id":"4923eca77b32178e","repo":"Leantime/leantime","slug":"invalid-plugin-name-s","errorCode":null,"errorMessage":"Invalid plugin name: %s","messagePattern":"Invalid plugin name: (.+?)","errorType":"console","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"app/Command/AbstractPluginCommand.php","lineNumber":76,"sourceCode":"    protected function getAllPlugins(): array\n    {\n        return array_values(\n            array_merge(\n                $this->plugins->getAllPlugins() ?: [],\n                $this->plugins->discoverNewPlugins(),\n            )\n        );\n    }\n\n    protected function getPlugin(string $name): InstalledPlugin\n    {\n        foreach ($this->getAllPlugins() as $plugin) {\n            if ($name === $plugin->name) {\n                return $plugin;\n            }\n        }\n\n        throw new RuntimeException(sprintf('Invalid plugin name: %s', $name));\n    }\n}\n","sourceCodeStart":58,"sourceCodeEnd":79,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Command/AbstractPluginCommand.php#L58-L79","documentation":"This warning comes from the custom artisan command `event:check-listeners` (app/Command/CheckEventListeners.php:156), which cross-references every listener registered with Leantime's EventDispatcher (both event and filter registries) against the set of events statically discovered by scanning app/ for hook dispatch sites. It fires when a registered listener name does not match any known event, even after wildcard (`*`, `?`) and inline `{RGX:...:RGX}` pattern matching. Because Leantime event names are string-convention based (`leantime.domain.{module}.{class}.{method}.{event}`), it almost always means a stale scan cache, a renamed/moved class, or a listener pointing at an event name that no longer exists.","triggerScenarios":"Running `php bin/leantime event:check-listeners` after: (1) a class was moved/renamed so its auto-generated dispatch event names changed while a register.php still listens on the old string; (2) a plugin or domain register.php registers a listener for an event dispatched dynamically (e.g., built via variables) that the static Documentor scan cannot see; (3) re-running with a stale storage/event_cache.json — events were removed from the code but the cache still lists them, or newly added events are invisible because the cache predates them; (4) a typo in an add_event_listener / add_filter_listener event string.","commonSituations":"During the ongoing string-event to class-based-event migration (renames in app/Domain/*/Services change event names silently); after pulling upstream changes that move classes between namespaces; after enabling/disabling commercial plugins whose register.php hooks core events; in CI pipelines that run this command against a storage/ directory committed or copied with an old event_cache.json.","solutions":["Re-run with a fresh scan to rule out cache staleness: `php bin/leantime event:check-listeners --clear-cache` (deletes storage/event_cache.json and rescans app/).","For each printed listener, grep the codebase for the dispatch site: `rg -n \"dispatch_event|dispatch_filter\" app/Domain | grep <eventNameFragment>` to confirm whether the event still exists.","If the class that dispatches the event was renamed/moved, update the string in the corresponding app/Domain/*/register.php (or plugin register.php) to the new `leantime.domain.<module>.<class>.<method>.<event>` name.","If the listener legitimately targets a dynamically-generated event name, rewrite it as a wildcard listener (e.g. `leantime.domain.tickets.*`) or an `{RGX:...:RGX}` pattern the checker understands.","If the listener is dead code, delete the EventDispatcher::add_event_listener / add_filter_listener call rather than silencing the warning."],"exampleFix":"// before (register.php) — class Tickets\\Services\\Tickets was renamed to TicketService\nevent_dispatcher()->add_event_listener(\n    'leantime.domain.tickets.services.tickets.updateTicket.ticket_updated',\n    NotifyProjectUsers::class\n);\n\n// after — matches the new namespace-derived event name\nevent_dispatcher()->add_event_listener(\n    'leantime.domain.tickets.services.ticket_service.update_ticket.ticket_updated',\n    NotifyProjectUsers::class\n);\n// or, resilient to renames:\nevent_dispatcher()->add_event_listener(\n    'leantime.domain.tickets.*.ticket_updated',\n    NotifyProjectUsers::class\n);","handlingStrategy":"validation","validationCode":"// Before trusting the report (or wiring CI), force a fresh scan so you validate against current code, not storage/event_cache.json\nArtisan::call('event:check-listeners', ['--clear-cache' => true]);\n$exit = Artisan::output(); // non-zero exit or 'Unmatched listeners found:' means register.php hooks need review\n\n// Before registering a listener for a convention-based event name, assert the dispatcher can ever see it:\n$eventName = 'leantime.domain.tickets.services.tickets.updateTicket.ticket_updated';\n$dispatchSiteExists = \\Illuminate\\Support\\Facades\\Process::run(\n    ['grep', '-rl', \"'ticket_updated'\", app_path('Domain/Tickets')]\n)->successful();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run `event:check-listeners --clear-cache` in CI on every PR so a rename that breaks a listener string fails the build, not production.","Prefer class-based events (see Files/Events/FileUploaded.php boilerplate) or wildcard listeners (`leantime.domain.<module>.*`) so moving a class does not silently orphan listeners.","Never hand-type event-name strings; derive them in a test from the same class/method constants used at the dispatch site.","Add storage/event_cache.json to .gitignore so stale caches are never shared between checkouts."],"tags":["events","console-command","static-analysis","code-migration","plugin-system"],"backgroundTag":"event-listener-name-mismatch","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}