If you are seeing a fatal error around EventDispatcher::dispatch() when running Drush, the problem is usually not Drupal content or configuration. It is a dependency mismatch: the Drush binary being executed was built against one set of Symfony components, while the Drupal project is loading another. Inherited servers, global Composer installs, and agency environments with multiple codebases are where this shows up most often. For business owners, that matters because a small maintenance task can suddenly become a deployment blocker.
The older quick fix was to add packages to Composer global until the error went away. You may still find advice telling you to run commands like composer global require symfony/event-dispatcher 3.4. That could work on a single legacy box, but it is brittle and no longer the fix I would recommend. Current Drush guidance is clear: install Drush per project with Composer and run the project's own binary. That keeps Drush, Drupal, PHP, and Symfony moving together instead of fighting each other.
Why this error happens
This class of fatal error became common when Symfony changed the dispatch() method signature. Older tooling expected one calling convention; newer Symfony versions used another. If your machine is executing a globally installed Drush while the site itself has different Symfony packages in its vendor tree, you can end up with Drush code calling a method signature it was not built for.
That is why the same server can behave differently across sites, and why one-off fixes in ~/.composer or $COMPOSER_HOME can create fresh problems later. Composer's global directory is shared across projects, so fixing Drush globally can silently change the dependency surface for every site on that user account.
The fix that still makes sense now
For a live Drupal project, install Drush inside the project and run that copy. This is the current supported installation model from Drush.
composer require drush/drush
vendor/bin/drush status
vendor/bin/drush versionIf your team wants the shorter drush command while working from the project root, add ./vendor/bin to your shell path in that project context. The important part is that the binary comes from the project, not from an old global Composer home or a system-wide directory such as /usr/local/bin.
For agencies and operations teams, this should be standardized in deployment scripts, CI jobs, and runbooks. Use the repo's own Drush binary in automation, and avoid assumptions about what happens to be installed on a developer laptop or shared server. That change alone removes a surprising amount of works-on-one-site, breaks-on-another behavior.
If you are stuck on a legacy stack
Sometimes the site is old, the hosting is constrained, and you need an operational fix before you can do a clean upgrade. In that case, diagnose the version conflict first instead of pinning random Symfony packages globally.
composer prohibits drush/drush ^13
composer prohibits symfony/event-dispatcher
composer config --global homeThe first two commands help you see what is blocking a supported Drush or Symfony version in the current project. The third shows where your Composer global configuration lives, which matters if a shared global toolchain is interfering with local projects.
If the site can only run an older Drush major, be explicit about that. Current Drush documentation shows that Drush 9 and 10 are long past end of life, and even Drush 11 is no longer supported. That does not mean a legacy site cannot work, but it does mean you should treat the setup as transitional and document the risk. For production teams, the right question is not how to keep an old global install alive forever, but what the smallest safe path is to a project-local, supportable toolchain.
If you absolutely must use a global workaround on a legacy server, pin the entire CLI toolchain knowingly and isolate it as much as possible. Do not copy old commands from forum posts one package at a time and hope the dependency resolver lands somewhere sensible. That approach is how teams end up with a box that can run one site's Drush command and break the next one.
What to standardize across teams
- Keep Drush in each Drupal project, not as a shared global dependency across many sites.
- Call
vendor/bin/drushin deployment, cron, CI, and recovery procedures. - Check Drupal, PHP, and Drush compatibility before upgrades rather than after a fatal error.
- Use Composer troubleshooting commands to understand blockers before changing version constraints.
- Remove undocumented server-level fixes that nobody remembers until the next outage window.
This error looks like a PHP problem, but operationally it is a dependency-governance problem. Once Drush is tied to the project and version decisions are documented, the failure mode becomes much easier to prevent. If you have a Drupal estate with mixed versions, fragile deployment scripts, or inherited server fixes, I can help turn it into a setup your team can actually support.
Need help with this kind of work?
Need a practical technical partner to stabilize Drupal operations, upgrades, and delivery across live sites? Get in touch with Greg.