fix: #3622011 Reject unvalidated tool input in AiFunctionCall plugins instead of crashing
Summary
A sweep for the same class of defect already fixed elsewhere in this module
-
unvalidated AI-agent tool input reaching Drupal Views internals and crashing, instead of being rejected with a readable tool error - found and fixed four separate, independently reproduced cases:
-
create_view_handler: a nonexistent table/field pair was accepted and saved as a broken handler, fataling later when the view renders. -
create_view: an entity type with no Views base table (any config entity, e.g.user_role) crashed with an uncaughtInvalidArgumentExceptiondeep inViewExecutable::mergeDefaults(). -
create_view_display:display_id: 'default'silently overwrote and wiped the existing default display's entire configuration -'default'is a real plugin ID, so it passed plugin-ID validation, butaddDisplay('default')always maps back to the ID'default'. Worse than a crash: silent, irreversible data loss. -
update_view_plugins: a structurally malformed value for an array-shaped option (e.g. a bare number forpager) crashed with an uncaughtTypeErrorinside a legacyViewsConfigUpdaterhook during$view->save(); unlike its siblingupdate_view_handler_option, this tool had no save-path guard at all.
Fixing the first one also exposed a pre-existing bug in
ViewsAgentHelper::getViewHandlerData(): its base-table lookup ran right
after getHandlers(), which switches the view's active display internally
and then switches it back to 'default' before returning - so a field
brought in through a relationship added on a non-default display was
wrongly rejected as invalid. Fixed by explicitly re-selecting the target
display before computing base tables.
All four were reproduced live against the unfixed code through the real,
running views_agent agent (fixes temporarily reverted, ddev drush agent views_agent) before being fixed, confirming these are genuine,
live-reachable defects and not artifacts of the test harness:
create_view/user_role: the raw internal exception (A valid cache entry key is required...) reached the user as the agent's entire reply.create_view_display/display_id: default: reported success with no error at all, while silently wiping a real field and a custom title from the existing default display (confirmed viadrush config:getimmediately after).update_view_plugins/pager: 42: not a caught tool error - the drush process itself crashed with a full PHP stack trace and terminated abnormally.create_view_handler/views_bulk_operations: silently accepted with no error; rendering the view afterward then crashed separately (TypeError: Unsupported operand types: array + null), far from the tool call that actually caused it.
Changes
src/Trait/ViewHandlerTrait.php:handleHandlerDataAndFieldSelection()now rejects a table/field pair not present in the view's real available fields.src/Service/ViewsAgent/ViewsAgentHelper.php: newgetEntityTypeBaseTable(), validated againstViews::viewsData();getViewHandlerData()now re-selects the target display before reading base tables.src/Plugin/AiFunctionCall/CreateView.php: validates the entity type's base table before creating the view.src/Plugin/AiFunctionCall/CreateViewDisplay.php: rejectsdisplay_id: 'default'.src/Plugin/AiFunctionCall/UpdateViewPlugins.php: wraps theoverrideOption()/save()path in acatch (\Throwable $e)guard, matchingUpdateViewHandlerOptions's existing one.- New Kernel Integration tests for all four, each proving the working view
(real field, real filtered/paginated query results) survives the
rejected attempt untouched:
tests/src/Kernel/Integration/CreateViewHandlerTableFieldValidationTest.php,CreateViewEntityTypeValidationTest.php,CreateViewDisplayDefaultRejectionTest.php,UpdateViewPluginsMalformedValueTest.php. - New Kernel plugin-level regression tests in
CreateViewHandlerTest.php,CreateViewTest.php,CreateViewDisplayTest.php,UpdateViewPluginsTest.php.
Test plan
- Each new regression test confirmed failing before its fix and passing after.
-
ConfigureRelationshipFieldsTest::testFieldsViaRelationships, which regressed when the table/field validation first went in, passes again after the display-scoping fix. - Full
ai_agents_viewsKernel suite: 87/87 tests, 965 assertions, 1 pre-existing unrelated skip, no failures. -
phpcs --standard=Drupal,DrupalPractice,phpstan analyse(module config) andcspell(module dictionary) all clean on every changed file. - All four fixes verified live against the real
views_agentAI agent both ways (ddev drush agent views_agent, an actual OpenAI-backed tool call through the agent's own loop): reproduced crashing/ corrupting on the unfixed code first (see Summary above), then confirmed clean - readable rejection, no crash, no data loss - on the fixed code with the exact same prompts. The relationship-fields case was verified the same way, live.
Closes #3622011
AI-Generated: Yes (Used Claude Code to sweep the remaining tools for this class of defect, diagnose each root cause, implement the fixes, write the regression and integration tests, and verify live against the shipped agent.)