Proposed: Surface exception messages from doExecute() instead of only the class name
### Problem/Motivation
When `ToolBase::execute()` catches a `\Throwable` from `doExecute()`, it intentionally suppresses the exception message and surfaces only the class name:
```php
catch (\Throwable $e) {
Error::logException($this->logger, $e);
$this->result = ExecutableResult::failure(new TranslatableMarkup(
'Tool execution failed: @type. The full error has been logged.',
['@type' => get_class($e)]
));
}
```
The LLM sees something like:
> Tool execution failed: Drupal\\Core\\Entity\\EntityStorageException. The full error has been logged.
This was a security precaution against leaking internals (SQL queries, file paths, connection strings). In practice though, the vast majority of exceptions thrown during `doExecute()` have messages that are:
- Authored by the tool developer intentionally (business logic errors)
- From Drupal's entity layer with semi-public messages (the same text shows in admin UI)
- Meaningful for LLM self-correction ("Cannot publish: entity is in draft state", "Node with ID 5 not found")
The truly sensitive exception types (PDO/database with SQL fragments, filesystem path leaks, service container internals) are infrastructure failures that don't help the LLM self-correct anyway.
Meanwhile, the `InvalidArgumentException | InputException` branch in the same method already surfaces the full message, and the AI connector's own `catch (\Exception $e)` also surfaces `$e->getMessage()` — making the `\Throwable` suppression inconsistent with the overall approach.
### Proposed resolution
Surface exception messages by default, with a length cap to prevent verbose stack traces or custom `__toString` implementations from flooding LLM context:
```php
catch (\Throwable $e) {
Error::logException($this->logger, $e);
$message = $e->getMessage();
// Cap at 500 chars to avoid leaking full stack traces from custom
// exceptions with verbose __toString implementations.
if (mb_strlen($message) > 500) {
$message = mb_substr($message, 0, 500) . '…';
}
$this->result = ExecutableResult::failure(new TranslatableMarkup(
'Tool execution failed: @message',
['@message' => $message]
));
}
```
This aligns the `\Throwable` branch with the existing `InvalidArgumentException | InputException` branch and with the AI connector's own exception handling, providing a consistent experience for all invokers.
### Alternatives considered
- **Marker interface (`SafeMessageExceptionInterface`)**: Tool authors opt-in exceptions as safe to surface. Rejected because it inverts the burden — the common case is "message is fine", and the rare dangerous ones (PDO, filesystem) are infrastructure failures where no message helps anyway.
- **Blocklist of suppressed exception types**: Suppress only `\PDOException`, `DatabaseExceptionWrapper`, etc. Feasible but over-engineering for a low-probability scenario.
- **Status quo**: Keep suppressing. Rejected because the inconsistency with other branches is confusing, and tool authors already write user-facing exception messages expecting them to be surfaced.
### AI usage (if applicable)
- [x] **AI Assisted Issue:** This issue was generated with AI assistance, but was reviewed and refined by the creator.
- [ ] **AI Assisted Code:** This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision.
- [ ] **AI Generated Code:** This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.
- [ ] **Vibe Coded:** This code was generated by an AI and has only been functionally tested.
issue
GitLab AI Context
Project: project/tool
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/tool/-/raw/1.0.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/tool
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD