Problem/Motivation
MenuLinkContent::postSave() calls addDefinition() on every save, so saving a trashed menu link writes a {menu_tree} row again. This happens outside the 'active' trash context, where storage stops filtering trashed entities: workspace publishing, any request on a trash admin route including bulk operations, purging, and core re-parenting the children of a link being removed.
The entity itself stays filtered out, so MenuLinkContent::getEntity() throws a PluginException on any menu walk that checks access. On multilingual sites that is every anonymous page render, because getTitle() and getDescription() resolve the translation through the entity.
Nothing cleans the row up afterwards. MenuTreeStorage::findNoLongerExistingLinks() only purges rows with discovered = 1, and rows written outside a rebuild have discovered = 0, so the row survives every menu rebuild until the link is purged or restored.
Proposed resolution
Decorate plugin.manager.menu.link and refuse addDefinition() and updateDefinition() for links backed by a trashed entity, dropping any row an earlier save left behind. Rebuilds collect their definitions inside the inner manager, out of reach of a decorator, so those are handled by dropping trashed links in hook_menu_links_discovered_alter().
Issue fork trash-3615837
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
amateescu commentedComment #6
amateescu commentedMerged into 3.1.x and cherry-picked the relevant parts to 3.x.