twentyhq/twenty · error · Exception
execute_tool failed for {name}
Error message
execute_tool failed for {name} What it means
Raised by TwentyMCP.call_tool after it routes a catalog tool (anything not in the 4 MCP-native tools) through execute_tool and the JSON-RPC envelope comes back with success: False. The thrown message uses the envelope's error or message field if present, falling back to `execute_tool failed for <name>` only when both are missing. This indicates the inner workspace tool ran but reported a business-logic failure, not a transport error.
Source
Thrown at packages/twenty-server/src/engine/core-modules/tool/tools/code-interpreter-tool/twenty-mcp-helper.const.ts:86
# companies == {'records': [...], 'count': '5'}
"""
if not self._available:
raise RuntimeError('Twenty MCP bridge not available. Missing requests library or credentials.')
if name in self._MCP_NATIVE_TOOLS:
return self._raw_mcp_call(name, arguments)
wrapped = self._raw_mcp_call('execute_tool', {
'toolName': name,
'arguments': arguments or {},
})
# execute_tool returns one of:
# success: { success: True, message, result: {...} }
# failure: { success: False, message, error }
# Raise on failure, unwrap on success, pass through unknown shapes.
if isinstance(wrapped, dict):
if wrapped.get('success') is False:
raise Exception(wrapped.get('error') or wrapped.get('message') or
f"execute_tool failed for {name}")
if 'result' in wrapped:
return wrapped['result']
return wrapped
def bulk_upsert(self, plural: str, records: list, batch_size: int = 200):
"""
Upsert many records in batches, paginating to completion.
This is the recommended write path for imports: upsert dedupes on the
object's unique fields (e.g. email) server-side, so re-running a partial
or timed-out import is idempotent. Batches are capped at 200 (the platform
maximum); the loop runs entirely server-side so the agent never pays the
per-batch context cost.
Args:
plural: Plural object name, e.g. 'companies', 'people'.
records: List of record dicts to upsert.View on GitHub (pinned to 1f5dd2bbd2)
Solutions
- Read the error/message field embedded in the exception text — it carries the inner tool's failure reason.
- For unique-constraint failures, switch to the upsert_many_* tool so the server dedupes on the object's unique fields.
- Validate argument shapes against the tool's schema (call `learn_tools` or inspect the catalog) before invoking.
- Use bulk_upsert(plural, records) which catches per-batch exceptions and reports them in errors[] rather than aborting the whole import.
Example fix
# before — single call aborts on the first bad record
for person in people:
twenty.call_tool('create_one_person', person) # raises execute_tool failed for create_one_person
# after — batched upsert dedupes and isolates per-batch failures
summary = twenty.bulk_upsert('people', people)
if summary['failed']:
print('partial failure:', summary['errors']) Defensive patterns
Strategy: retry
Validate before calling
# Pre-validate argument shape against the catalog before calling.
from typing import Any
def validate_args_against_catalog(catalog: dict, tool_name: str, args: dict) -> list[str]:
schema = catalog.get(tool_name, {}).get('inputSchema', {})
required = schema.get('required', [])
errors = []
for field in required:
if field not in args:
errors.append(f'{tool_name}: missing required field {field}')
return errors
# usage:
catalog = twenty.call_tool('learn_tools', {}) # MCP-native, returns tool catalog
errs = validate_args_against_catalog(catalog, 'create_one_person', person)
if errs:
raise ValueError(errs[0]) Type guard
from typing import Any
def is_execute_tool_failure(envelope: Any) -> bool:
return isinstance(envelope, dict) and envelope.get('success') is False Try / catch
try:
rec = twenty.call_tool('create_one_company', company)
except Exception as e:
msg = str(e)
if 'execute_tool failed' in msg:
# business-logic failure from the inner tool — do NOT blind-retry; fix args
if 'unique' in msg.lower() or 'duplicate' in msg.lower():
rec = twenty.call_tool('upsert_one_company', company) # switch to upsert to dedupe
else:
raise
else:
raise Prevention
- Prefer bulk_upsert(plural, records) for writes — it isolates per-batch failures instead of aborting on the first error.
- Call learn_tools once and cache the catalog; validate argument shapes against each tool's inputSchema before invoking.
- Resolve relation ids via lookup_by before writing so create/update calls reference valid ids.
When it happens
Trigger: Calling a workspace catalog tool (e.g. create_one_company, upsert_many_people) whose arguments fail server-side validation, violate a unique constraint, reference a non-existent relation id, or hit a permissions error for the token's workspace. The execute_tool envelope wraps that failure as { success: False, error/message: ... } and call_tool re-raises it.
Common situations: LLM-generated calls with wrong field names or types; upserting records that collide on a unique field without the right dedupe key; calling a tool the API token's user lacks permission for; passing a record id from a different workspace.
Related errors
- MCP Error: {result['error'].get('message', 'Unknown error')}
- Twenty MCP bridge not available. Missing requests library or
- {input_dir} is not a directory
- {output_file} must be a .docx, .pptx, or .xlsx file
- Slide index {idx} out of range (0-{total_slides - 1})
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/74b1a15cacc2f4ef.
Report an issue: GitHub.