Problem/Motivation
The ai_agents_views:update_view_plugins tool had been silently ineffective on any non-default display for its entire lifetime - the single largest-blast-radius defect found while testing the shipped Views Agent against a live LLM. A change that looked completely correct in a config dump could have zero real effect at runtime, on options as consequential as access.
Confirmed with a functional access check: an admin view built entirely through the agent, with its page_1</Fix update_view_plugins silently ineffective on non-default displayscode> display's <code>access option explicitly set to a permission-gated perm plugin, still let a real user account without that permission load the display - the access restriction the config claimed to have simply never took effect.
Steps to reproduce
- Create a View with a non-default display (e.g. a page display) that is still inheriting most of its options from the
defaultdisplay - true of any freshly created page/block/feed display until something explicitly overrides them. - Call
ai_agents_views:update_view_pluginsto set that display'saccessoption to something restrictive, e.g.{"type":"perm","options":{"perm":"access content overview"}}. - Load the View and inspect
$view->getDisplay('page_1')['display_options']['access']- it shows the new, correct value. - Now check whether access is actually enforced:
$view->getExecutable()->access('page_1', $account)for a real account without the granted permission.
Expected: the account without the permission is denied access.
Actual (before this fix): the account is granted access - the display still functionally uses whatever access plugin the default display has (frequently none), regardless of what was just saved on page_1.
Root cause
execute() wrote the new value directly into the display's raw config array:
$display = &$view->getDisplay($display_id); // Update the view display option with the provided config. $display['display_options'][$display_option] = $config;
Views' own inheritance mechanism ("defaultable sections") lets most display options on any non-default display be either explicitly set on that display, or silently inherited from the default display - controlled by a separate defaults flag stored alongside the option itself. A freshly created page/block/feed display starts out inheriting nearly everything from default, until something explicitly overrides a given option and clears that flag.
The raw write above only ever touched the option's own value - it never cleared the corresponding defaults flag. So the new value sat in the saved config, technically present and correct-looking, while Views' own DisplayPluginBase::getOption()/getPlugin() - which check the defaults flag first - kept resolving to whatever the default display already had, at every real request. This affected every option that participates in this inheritance mechanism: access, cache, style, row, and more.
Proposed resolution
Switch to Views' own DisplayPluginBase::overrideOption(), which correctly clears the inheritance flag before writing the new value:
// Use overrideOption(), not a raw config write: a display can still be // inheriting this option from the default display, and a raw write // would save the new value without making it take effect at runtime. $view_executable = $view->getExecutable(); $view_executable->initDisplay(); $view_executable->displayHandlers->get($display_id)->overrideOption($display_option, $config); $this->addViewTag($view, 'ai_agents_views_updated'); $view->save();
This is a no-op for options that were never defaultable to begin with (e.g. path, use_admin_theme), so it takes effect correctly either way.
Remaining tasks
- File this issue on drupal.org.
- Open an MR with the fix above.
Note: Views groups several options together as a single defaultable section (for example style/row, or use_more/use_more_always/use_more_text) - overrideOption() correctly un-defaults the whole group, not just the option requested, which is expected Views behavior but produces its own follow-on side effect. Filed and fixed separately, since it's a distinct, narrower concern with its own regression test.
Test coverage added locally
New Integration test CreateAdminAuditViewTest::testAgentTools() builds an admin content-listing view through the real tool chain (bulk-operations field, operations field, a permission-gated access option on a page display) and proves, with a real editor account and a real visitor account, that access genuinely gates the display - not just that the saved config looks right. It also confirms the bulk-operations/operations fields resolve to real, working handlers when the view is actually rendered.
The pre-existing CreateAdminViewTest needed one assertion loosened: row's exact array previously asserted just {"type":"fields"}; with this fix, overriding the linked style option also completes row's schema-declared options default (an empty array) through the same override mechanism - a harmless completion of the same value, not a different one, so only the type key is asserted now.
Full suite with this fix applied (isolated on its own branch): 2/2 kernel tests passing (70 assertions) across both Integration test files, no failures; the pre-existing UpdateViewPluginsTest suite (14 tests, 116 assertions) also confirmed unaffected.
API changes
None. This is a bug fix to existing, already-broken behavior - no new context parameters, no signature changes, no config schema changes.
Issue fork ai_agents_views-3621953
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
jibranComment #5
jibranCommitted and pushed to 1.0.x