Leantime/leantime · warning · RuntimeException

Invalid plugin name: %s

Error message

Invalid plugin name: %s

What it means

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.

Source

Thrown at app/Command/AbstractPluginCommand.php:76

    protected function getAllPlugins(): array
    {
        return array_values(
            array_merge(
                $this->plugins->getAllPlugins() ?: [],
                $this->plugins->discoverNewPlugins(),
            )
        );
    }

    protected function getPlugin(string $name): InstalledPlugin
    {
        foreach ($this->getAllPlugins() as $plugin) {
            if ($name === $plugin->name) {
                return $plugin;
            }
        }

        throw new RuntimeException(sprintf('Invalid plugin name: %s', $name));
    }
}

View on GitHub (pinned to 9a9f49f100)

Solutions

  1. 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/).
  2. 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.
  3. 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.
  4. 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.
  5. If the listener is dead code, delete the EventDispatcher::add_event_listener / add_filter_listener call rather than silencing the warning.

Example fix

// before (register.php) — class Tickets\Services\Tickets was renamed to TicketService
event_dispatcher()->add_event_listener(
    'leantime.domain.tickets.services.tickets.updateTicket.ticket_updated',
    NotifyProjectUsers::class
);

// after — matches the new namespace-derived event name
event_dispatcher()->add_event_listener(
    'leantime.domain.tickets.services.ticket_service.update_ticket.ticket_updated',
    NotifyProjectUsers::class
);
// or, resilient to renames:
event_dispatcher()->add_event_listener(
    'leantime.domain.tickets.*.ticket_updated',
    NotifyProjectUsers::class
);
Defensive patterns

Strategy: validation

Validate before calling

// Before trusting the report (or wiring CI), force a fresh scan so you validate against current code, not storage/event_cache.json
Artisan::call('event:check-listeners', ['--clear-cache' => true]);
$exit = Artisan::output(); // non-zero exit or 'Unmatched listeners found:' means register.php hooks need review

// Before registering a listener for a convention-based event name, assert the dispatcher can ever see it:
$eventName = 'leantime.domain.tickets.services.tickets.updateTicket.ticket_updated';
$dispatchSiteExists = \Illuminate\Support\Facades\Process::run(
    ['grep', '-rl', "'ticket_updated'", app_path('Domain/Tickets')]
)->successful();

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/4923eca77b32178e. Report an issue: GitHub.