{"record":{"id":"61fa7e8795d98fee","repo":"ComposioHQ/composio","slug":"context-description-is-required","errorCode":null,"errorMessage":"{context}: description is required","messagePattern":"(.+?): description is required","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"python/composio/core/models/custom_tool.py","lineNumber":178,"sourceCode":"    slug: str,\n    *,\n    name: str,\n    description: str,\n    input_params: t.Type[BaseModel],\n    execute: CustomToolExecuteFn,\n    extends_toolkit: t.Optional[str] = None,\n    output_params: t.Optional[t.Type[BaseModel]] = None,\n    preload: t.Optional[bool] = None,\n) -> CustomTool:\n    \"\"\"Internal: create and validate a CustomTool.\"\"\"\n    context = \"experimental.tool\"\n\n    _validate_slug(slug, context)\n\n    if not name:\n        raise ValidationError(f\"{context}: name is required\")\n    if not description:\n        raise ValidationError(f\"{context}: description is required\")\n\n    if not isinstance(input_params, type) or not issubclass(input_params, BaseModel):\n        raise ValidationError(\n            f\"{context}: input_params must be a Pydantic BaseModel subclass. \"\n            f\"Tool input parameters are always an object with named properties.\"\n        )\n\n    try:\n        from pydantic import RootModel\n\n        if issubclass(input_params, RootModel):\n            raise ValidationError(\n                f\"{context}: input_params must be a regular BaseModel with named fields, \"\n                f\"not a RootModel. Tool input parameters are always an object with \"\n                f\"named properties.\"\n            )\n    except ImportError:\n        pass","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/core/models/custom_tool.py#L160-L196","documentation":"Raised when creating a custom tool with an empty description. Descriptions are what LLMs use to decide when to call the tool, so Composio requires them.","triggerScenarios":"Calling Tool(...) or the @tool decorator without a docstring-derived description, or with description=\"\".","commonSituations":"Forgetting the docstring on a decorated function and not passing description explicitly; stripping docstrings in production builds or with python -OO; config-driven tool definitions missing the description key.","solutions":["Add a clear description parameter or a docstring: for @tool-decorated functions the function docstring becomes the description","Check that docstrings survive your build (avoid -OO / aggressive minifiers) if you rely on implicit descriptions","Validate generated tool definitions contain a non-empty description before registration"],"exampleFix":"# before\ndef search(q: str):\n    pass  # no docstring\n\n# after\ndef search(q: str):\n    \"\"\"Search the user's connected documents for a query.\"\"","handlingStrategy":"validation","validationCode":"if not description:\n    description = (fn.__doc__ or \"\").strip() or f\"Execute {fn.__name__}.\"\nassert description, \"description required\"","typeGuard":"def has_description(desc: str | None) -> bool:\n    return bool(desc and desc.strip())","tryCatchPattern":"try:\n    Tool(description=description, ...)\nexcept ValidationError:\n    Tool(description=fn.__doc__ or f\"Executes {fn.__name__}.\", ...)","preventionTips":["Write a docstring on every tool function — it becomes the description","Never build with python -OO if you rely on docstrings","Validate generated tool configs contain descriptions in CI"],"tags":["python","custom-tools","required-field","llm-tooling"],"backgroundTag":"missing-required-field","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}