ActionPluginDeriver crashes AI features when an action plugin lacks an instance class
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3585335. -->
Reported by: [mtift](https://www.drupal.org/user/751908)
Related to !1520
>>>
<p>[Tracker]<br>
<strong>Update Summary: </strong>New bug report — defensive fix proposed<br>
<strong>Short Description: </strong>ActionPluginDeriver crashes the chat explorer when any ECA action plugin lacks a class<br>
<strong>Check-in Date: </strong>04/23/2026<br>
<em>Metadata is used by the <a href="https://www.drupalstarforge.ai/" title="AI Tracker">AI Tracker.</a> Docs and additional fields <a href="https://www.drupalstarforge.ai/ai-dashboard/docs" title="AI Issue Tracker Documentation">here</a>.</em><br>
[/Tracker]</p>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p><code>ActionPluginDeriver::getDerivativeDefinitions()</code> iterates every ECA action plugin definition and calls <code>createInstance()</code> on each one to discover available function-call tools for AI providers. If any single action plugin definition lacks an instance class, the call throws <code>PluginException</code>, which propagates up and breaks the entire AI chat explorer page (and any other AI feature that enumerates function calls).</p>
<p>One bad action plugin anywhere in the site — including ones contributed by old or deprecated contrib modules — takes out all AI chat tooling.</p>
<p>The trigger in our case was <code>file_entity</code> 8.x-1.14, which declares <code>pathauto_file_update_action</code> via the legacy <code>hook_action_info_alter()</code> without an instance class. That's a real bug in <code>file_entity</code> (we'll file separately), but <code>drupal/ai</code> shouldn't crash because of it — it should skip the broken definition and continue.</p>
<p>Stack trace:</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br>Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Exception</span><span style="color: #007700">\</span><span style="color: #0000BB">PluginException</span><span style="color: #007700">: </span><span style="color: #0000BB">The plugin </span><span style="color: #007700">(</span><span style="color: #0000BB">pathauto_file_update_action</span><span style="color: #007700">) </span><span style="color: #0000BB">did not specify an instance </span><span style="color: #007700">class. </span><span style="color: #0000BB">in Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Factory</span><span style="color: #007700">\</span><span style="color: #0000BB">DefaultFactory</span><span style="color: #007700">::</span><span style="color: #0000BB">getPluginClass</span><span style="color: #007700">() (</span><span style="color: #0000BB">line 79 of core</span><span style="color: #007700">/</span><span style="color: #0000BB">lib</span><span style="color: #007700">/</span><span style="color: #0000BB">Drupal</span><span style="color: #007700">/</span><span style="color: #0000BB">Component</span><span style="color: #007700">/</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">/</span><span style="color: #0000BB">Factory</span><span style="color: #007700">/</span><span style="color: #0000BB">DefaultFactory</span><span style="color: #007700">.</span><span style="color: #0000BB">php</span><span style="color: #007700">).<br><br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Factory</span><span style="color: #007700">\</span><span style="color: #0000BB">ContainerFactory</span><span style="color: #007700">-></span><span style="color: #0000BB">createInstance</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">83</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">PluginManagerBase</span><span style="color: #007700">-></span><span style="color: #0000BB">createInstance</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">178</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">eca</span><span style="color: #007700">\</span><span style="color: #0000BB">PluginManager</span><span style="color: #007700">\</span><span style="color: #0000BB">Action</span><span style="color: #007700">-></span><span style="color: #0000BB">createInstance</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">67</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">ai</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">AiFunctionCall</span><span style="color: #007700">\</span><span style="color: #0000BB">Derivative</span><span style="color: #007700">\</span><span style="color: #0000BB">ActionPluginDeriver</span><span style="color: #007700">-></span><span style="color: #0000BB">getDerivativeDefinitions</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">101</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Discovery</span><span style="color: #007700">\</span><span style="color: #0000BB">DerivativeDiscoveryDecorator</span><span style="color: #007700">-></span><span style="color: #0000BB">getDerivatives</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">87</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Component</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">Discovery</span><span style="color: #007700">\</span><span style="color: #0000BB">DerivativeDiscoveryDecorator</span><span style="color: #007700">-></span><span style="color: #0000BB">getDefinitions</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">337</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">Core</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">DefaultPluginManager</span><span style="color: #007700">-></span><span style="color: #0000BB">findDefinitions</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">33</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">ai</span><span style="color: #007700">\</span><span style="color: #0000BB">Service</span><span style="color: #007700">\</span><span style="color: #0000BB">FunctionCalling</span><span style="color: #007700">\</span><span style="color: #0000BB">FunctionCallPluginManager</span><span style="color: #007700">-></span><span style="color: #0000BB">getDefinitions</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">202</span><span style="color: #007700">)<br></span><span style="color: #0000BB">Drupal</span><span style="color: #007700">\</span><span style="color: #0000BB">ai_api_explorer</span><span style="color: #007700">\</span><span style="color: #0000BB">Plugin</span><span style="color: #007700">\</span><span style="color: #0000BB">AiApiExplorer</span><span style="color: #007700">\</span><span style="color: #0000BB">ChatGenerator</span><span style="color: #007700">-></span><span style="color: #0000BB">buildForm</span><span style="color: #007700">() (</span><span style="color: #0000BB">Line</span><span style="color: #007700">: </span><span style="color: #0000BB">60</span><span style="color: #007700">)<br></span><span style="color: #0000BB">?></span></span></pre></div>
<h4 id="summary-steps-reproduce">Steps to reproduce (required for bugs, but not feature requests)</h4>
<p>Environment:</p>
<ul>
<li>Drupal core 10.x</li>
<li><code>drupal/ai</code> 1.3.3</li>
<li><code>ai_api_explorer</code> enabled</li>
<li>ECA 2.1.x enabled</li>
<li>Any module that registers an ECA action plugin definition without a <code>class</code> key (e.g., <code>file_entity</code> 8.x-1.14 with <code>pathauto</code> enabled)</li>
</ul>
<p>Steps:</p>
<ol>
<li>Navigate to <code>/admin/config/ai/explorers/chat_generator</code>.</li>
<li>Observe a WSOD with the <code>PluginException</code> above.</li>
</ol>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>Make <code>ActionPluginDeriver::getDerivativeDefinitions()</code> defensive: skip action definitions that lack an instance class so one bad third-party plugin can't take out every AI feature that enumerates function calls.</p>
<p>Minimal fix in <code>src/Plugin/AiFunctionCall/Derivative/ActionPluginDeriver.php</code>:</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">foreach (</span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">actionManager</span><span style="color: #007700">-></span><span style="color: #0000BB">getDefinitions</span><span style="color: #007700">() as </span><span style="color: #0000BB">$id </span><span style="color: #007700">=> </span><span style="color: #0000BB">$action_definition</span><span style="color: #007700">) {<br> </span><span style="color: #FF8000">// Skip plugin definitions that lack an instance class. Some legacy<br> // contrib modules register actions via hook_action_info_alter() without<br> // providing a class; those cannot be instantiated.<br> </span><span style="color: #007700">if (empty(</span><span style="color: #0000BB">$action_definition</span><span style="color: #007700">[</span><span style="color: #DD0000">'class'</span><span style="color: #007700">])) {<br> continue;<br> }<br> if (</span><span style="color: #0000BB">in_array</span><span style="color: #007700">(</span><span style="color: #0000BB">$action_definition</span><span style="color: #007700">[</span><span style="color: #DD0000">'type'</span><span style="color: #007700">], </span><span style="color: #0000BB">$supported_action_types</span><span style="color: #007700">, </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">) || </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">entityTypeManager</span><span style="color: #007700">-></span><span style="color: #0000BB">getDefinition</span><span style="color: #007700">(</span><span style="color: #0000BB">$action_definition</span><span style="color: #007700">[</span><span style="color: #DD0000">'type'</span><span style="color: #007700">], </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">)) {<br> </span><span style="color: #FF8000">// ... existing logic, including the createInstance() call ...<br> </span><span style="color: #007700">}<br>}<br></span><span style="color: #0000BB">?></span></span></pre></div>
<p>A <code>try/catch</code> around <code>createInstance()</code> would be a defense-in-depth alternative if maintainers prefer to also tolerate other instantiation failures.</p>
<p>The same defensive pattern likely belongs in any other deriver in the module that enumerates third-party plugins it doesn't own.</p>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li>Decide on guard style: pre-check on <code>['class']</code>, <code>try/catch</code>, or both</li>
<li>Audit other derivers in <code>src/Plugin/AiFunctionCall/Derivative/</code> for the same fragility</li>
<li>Patch + test against a site with a deliberately broken action plugin</li>
<li>Reviews</li>
</ul>
<h3>Related issues</h3>
<ul>
<li><span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-8"><a href="https://www.drupal.org/project/ai/issues/3585328" title="Status: Needs review">#3585328: Undefined array key "#type" warnings break AJAX in AbstractModelFormBase::updateModelForm()</a></span> — separate <code>drupal/ai</code> bug we filed in the same session, surfaced while configuring a Vertex chat model</li>
</ul>
<h3>Optional: Other details as applicable (e.g., User interface changes, API changes, Data model changes)</h3>
<p>No UI, API, or data model changes. The fix makes the deriver resilient to malformed third-party plugin definitions.</p>
<h3 id="summary-ai-usage">AI usage (if applicable)</h3>
<p>[x] AI Assisted Issue<br><br>
This issue was generated with AI assistance, but was reviewed and refined by the creator.</p>
<p>[ ] AI Assisted Code<br><br>
[ ] AI Generated Code<br><br>
[ ] Vibe Coded</p>
issue