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

  1. Shorten the custom tool's slug
  2. Shorten the parent toolkit slug if the prefix is what pushes it over
  3. 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

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


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/f0e3223e70a1aad6. Report an issue: GitHub.