Fatal error when ToolBase::getResult() is called before result is initialised
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3571742. -->
Reported by: [yautja_cetanu](https://www.drupal.org/user/626050)
Related to !82 !81
>>>
<p>Title: Fatal error when ToolBase::getResult() is called before result is initialised</p>
<p>Note: This issue was created with ChatGPT, and I just couldn't get it to do what I wanted with this issue, so apologies for the mess. There is the error message and the attempt to explain it below.</p>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>The Tool module can throw a fatal PHP error when<br>
<code>Drupal\tool\Tool\ToolBase::getResult()</code> is called before the typed<br>
<code>$result</code> property has been initialised.</p>
<p>This occurs in integrations that expect a tool to expose readable output. For example, when tools are invoked via tool_ai_connector. If the tool does not successfully execute, or exits early, the result may never be set.</p>
<p>Because <code>$result</code> is a typed property, accessing it before initialisation causes a hard PHP error.</p>
<h4 id="summary-steps-reproduce">Steps to reproduce</h4>
<ol>
<li> Create or configure a tool that extends <code>ToolBase</code>. </li>
<li> Ensure the tool does not always set a result. </li>
<li> Invoke the tool via an integration that calls <code>getResult()</code> or <code>getReadableOutput()</code>. </li>
</ol>
<p>Observed error:</p>
<pre> Error: Typed property Drupal\tool\Tool\ToolBase::$result must not be accessed before initialization </pre><h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>Ensure ToolBase can be safely consumed when no result is produced.</p>
<p>One option is to initialise <code>$result</code> in the base class.</p>
<p>Another option is to guard <code>getResult()</code> against an uninitialised state.</p>
<p>For example, return <code>null</code> or throw a controlled exception instead of triggering a fatal error.</p>
<p>Tool implementations should have a clear contract for when a result must be set.</p>
<h3 id="Error Output">Error Output</h3>
<pre>Error: Typed property Drupal\tool\Tool\ToolBase::$result must not be accessed before initialization in Drupal\tool\Tool\ToolBase->getResult() (line 157 of /var/www/html/web/modules/contrib/tool/src/Tool/ToolBase.php).<br><br>#0 /var/www/html/web/modules/contrib/tool/modules/tool_ai_connector/src/Plugin/AiFunctionCall/ToolPluginBase.php(131): Drupal\tool\Tool\ToolBase->getResult()<br>#1 /var/www/html/web/modules/contrib/ai_agents/src/PluginBase/AiAgentEntityWrapper.php(1308): Drupal\tool_ai_connector\Plugin\AiFunctionCall\ToolPluginBase->getReadableOutput()<br>#2 /var/www/html/web/modules/contrib/ai/modules/ai_assistant_api/src/Service/AgentRunner.php(91): Drupal\ai_agents\PluginBase\AiAgentEntityWrapper->toArray()<br>#3 /var/www/html/web/modules/contrib/ai/modules/ai_assistant_api/src/AiAssistantApiRunner.php(319): Drupal\ai_assistant_api\Service\AgentRunner->runAsAgent()<br>#4 /var/www/html/web/modules/contrib/ai/modules/ai_chatbot/src/Controller/DeepChatApi.php(203): Drupal\ai_assistant_api\AiAssistantApiRunner->process()<br>#5 [internal function]: Drupal\ai_chatbot\Controller\DeepChatApi->api()<br>#6 /var/www/html/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()<br>#7 /var/www/html/web/core/lib/Drupal/Core/Render/Renderer.php(634): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()<br>#8 [internal function]: Drupal\Core\Render\Renderer::Drupal\Core\Render\{closure}()<br>#9 /var/www/html/web/core/lib/Drupal/Core/Render/Renderer.php(649): Fiber->resume()<br>#10 /var/www/html/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\Core\Render\Renderer->executeInRenderContext()<br>#11 /var/www/html/web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()<br>#12 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(183): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()<br>#13 /var/www/html/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw()<br>#14 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()<br>#15 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()<br>#16 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()<br>#17 /var/www/html/web/core/modules/big_pipe/src/StackMiddleware/ContentLength.php(32): Drupal\Core\StackMiddleware\ContentLength->handle()<br>#18 /var/www/html/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(118): Drupal\big_pipe\StackMiddleware\ContentLength->handle()<br>#19 /var/www/html/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(92): Drupal\page_cache\StackMiddleware\PageCache->pass()<br>#20 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\page_cache\StackMiddleware\PageCache->handle()<br>#21 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()<br>#22 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(53): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()<br>#23 /var/www/html/web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(54): Drupal\Core\StackMiddleware\AjaxPageState->handle()<br>#24 /var/www/html/web/core/lib/Drupal/Core/DrupalKernel.php(745): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()<br>#25 /var/www/html/web/index.php(19): Drupal\Core\DrupalKernel->handle()<br>#26 {main}</pre><h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li> Agree expected behaviour when no result is produced. </li>
<li> Add defensive handling in <code>ToolBase</code>. </li>
<li> Add test coverage for this scenario. </li>
</ul>
<h3 id="summary-ui-changes">User interface changes</h3>
<p>None.</p>
<h3 id="summary-api-changes">API changes</h3>
<p>None intended.</p>
<h3 id="summary-data-model-changes">Data model changes</h3>
> Related issue: [Issue #3558327](https://www.drupal.org/node/3558327)
issue