Problem/Motivation

Local task tabs that are derived from overridable configuration are cached without an "active domain" axis, so their output leaks across domains. The user-visible symptom is on /admin/structure/block: the "default theme" tab (derived by Drupal\block\Plugin\Derivative\ThemeLocalTask) shows the same theme name on every domain, even when each domain has a different system.theme.default override registered through domain_config_ui.

Example on a multi-domain instance with system.theme registered as overridable:

  • Domain A: system.theme override — default = olivero
  • Domain B: system.theme override — default = drupal11
  • Domain C: system.theme override — default = bootstrap5

Visiting /admin/structure/block on each domain shows the same first tab everywhere instead of "Olivero" / "Drupal 11" / "Bootstrap5" respectively. Whichever domain hits the cache cold first wins for everyone.

Steps to reproduce

  1. Install domain, domain_config, domain_config_ui.
  2. Register system.theme as overridable for at least two domains.
  3. Save a different default theme per domain.
  4. Clear caches.
  5. Visit /admin/structure/block on each domain in turn — the "default theme" tab name does not change.

Root cause

Drupal\Core\Menu\LocalTaskManager sets its plugin definition cache key based on language only:

// core/lib/Drupal/Core/Menu/LocalTaskManager.php
$this->setCacheBackend($cache, 'local_task_plugins:' . $language_manager->getCurrentLanguage()->getId(), ['local_task']);

Derivers like ThemeLocalTask read system.theme.default via themeHandler->getDefault(), which IS domain-overridden — but the resulting plugin definitions are cached under a key that does not include the active domain id. The first domain to populate the cache freezes the answer for the rest.

Proposed resolution

Decorate plugin.manager.menu.local_task in the domain module so the plugin definition cache key includes the active domain id, e.g. local_task_plugins:LANGCODE:DOMAIN_ID (where LANGCODE is the active interface language and DOMAIN_ID is the active domain machine name). Alternative: invalidate the local_task cache tag on domain switch — works but evicts more than necessary on every domain change.

Remaining tasks

  • Decide between cache-key decoration vs tag-invalidation.
  • Patch + functional test that asserts the "default theme" tab title differs across two domains with distinct system.theme overrides.
  • Audit other plugin managers that cache discovery output keyed only by language for the same class of bug (menu links, local actions, contextual links, etc.).

Related

  • #3587744 — separate but adjacent: per-domain config display on EntityForm and admin lists.

Issue fork domain-3588057

Command icon 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

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review

mably changed the visibility of the branch 3588057-local-task-tabs to hidden.

mably’s picture

Status: Needs review » Closed (won't fix)

Closing as won't fix in favour of the submodule-scoped fix in domain_extras: #3588108 (!33).

Reasons for not landing the cache-key change in domain's base module:

  • Scope of benefit is partial. The local task cache fragmentation only matters for sites that override per-domain configuration affecting task definitions. Most domain installs don't. Putting it in a submodule makes the trade-off explicit at install time.
  • Performance cost is real and additive. The cache fragments by language × domain × manager. Three managers (LocalTaskManager, LocalActionManager, ContextualLinkManager) × N domains × M languages compounds quickly. Sites that don't need it shouldn't pay for it.
  • Engineering simplicity. A submodule is one service-provider swap and a kernel test per manager. A base-module config flag would mean either a compile-time gate (chicken-and-egg with container compile) or a runtime gate inside the swapped class (still pays the cache miss). The submodule sidesteps both.
  • Consistent with the established domain_extras pattern. domain_config_entity_ui is already an opt-in submodule for the read-side / list-builder fold; domain_menu_extras follows the same shape: install the submodule, get the fix.

MR !367 will be closed. Tracking work continues in #3588108.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.