ComposioHQ/composio · error · RuntimeError
Cache directory {directory} is not writable please provide a
Error message
Cache directory {directory} is not writable please provide a path that is writable using {ENV_LOCAL_CACHE_DIRECTORY} environment variable. What it means
A custom tool's final slug (after the SDK applies prefixing/suffixing rules) exceeds MAX_SLUG_LENGTH characters. The slug length is enforced on the computed final slug, not just the handle slug you provided, so short-looking slugs can still overflow once toolkit prefixes are appended.
Source
Thrown at python/composio/core/models/_files.py:145
def ensure_cache_directory() -> Path:
"""Create the cache directory on first use and check that it is writable.
This used to run at module import time, so a bare ``import composio``
raised ``RuntimeError`` on any read-only filesystem -- AWS Lambda,
distroless containers, ``ProtectHome=true`` systemd units -- even for
programs that never touched a file. Deferring it to first use keeps the
same check, and the same error message, for the callers that actually
need the directory.
"""
directory = get_cache_directory()
try:
directory.mkdir(parents=True, exist_ok=True)
if not os.access(directory, os.W_OK):
raise OSError
except OSError as e:
raise RuntimeError(
f"Cache directory {directory} is not writable please "
f"provide a path that is writable using {ENV_LOCAL_CACHE_DIRECTORY} "
"environment variable."
) from e
return directory
def __getattr__(name: str) -> Path:
"""Keep the historical module-level path constants working, but lazily.
``LOCAL_CACHE_DIRECTORY`` and ``LOCAL_OUTPUT_FILE_DIRECTORY`` used to be
computed at import time. They are now resolved on attribute access
instead (PEP 562), so importing this module no longer touches the
filesystem or depends on the environment, and both constants observe a
``COMPOSIO_CACHE_DIR`` that was set after import.
"""
if name == "LOCAL_CACHE_DIRECTORY":
return get_cache_directory()View on GitHub (pinned to 64b1b85502)
Solutions
- Shorten the custom tool's slug
- Shorten the parent toolkit slug if the prefix is what pushes it over
- Split the tool into multiple shorter-named tools
Example fix
# before tool = CustomTool(slug='VERY_LONG_TOOL_NAME_THAT_EXCEEDS_THE_LIMIT...', ...) # after tool = CustomTool(slug='ANALYZE_TICKETS', ...)
Defensive patterns
Strategy: validation
Validate before calling
from composio.core.models.custom_tool import MAX_SLUG_LENGTH
final = f'{toolkit_slug}_{handle.slug}' if toolkit_slug else handle.slug
if len(final) > MAX_SLUG_LENGTH:
handle.slug = handle.slug[:MAX_SLUG_LENGTH - len(toolkit_slug) - 1] Type guard
def slug_fits(handle_slug: str, toolkit_prefix: str = '') -> bool:
final = f'{toolkit_prefix}{handle_slug}' if toolkit_prefix else handle_slug
return len(final) <= MAX_SLUG_LENGTH Try / catch
try:
tools_map = build_custom_tools_map(tools, toolkits)
except ValidationError as e:
if 'exceeds' in str(e) and 'characters' in str(e):
# shorten slugs and rebuild
shorten_slugs(tools)
tools_map = build_custom_tools_map(tools, toolkits)
else:
raise Prevention
- Keep custom tool slugs short (<= 40 chars) by convention
- Account for toolkit slug prefixes when choosing tool slugs
When it happens
Trigger: Registering a CustomTool whose handle.slug, once combined with its toolkit slug/prefix, produces a final slug longer than MAX_SLUG_LENGTH; occurs in build_custom_tools_map / build_custom_tools_map_from_response during create or use.
Common situations: Long descriptive tool names like 'AGENT_ANALYZE_CUSTOMER_SUPPORT_TICKETS_V2'; nesting tools under a toolkit with a long slug; generated slugs from function names.
Related errors
- module {__name__!r} has no attribute {name!r}
- Failed to upload to S3: {_sanitize_url_for_logging(url)}. Er
- Failed to upload to S3. Status: {response.status_code}. This
- Could not determine a home directory to store the Composio c
- Request timed out fetching URL: {_sanitize_url_for_logging(u
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/f0e3223e70a1aad6.
Report an issue: GitHub.