ComposioHQ/composio · error · AttributeError

module {__name__!r} has no attribute {name!r}

Error message

module {__name__!r} has no attribute {name!r}

What it means

Two custom tools resolve to the same final slug, so the registry cannot uniquely identify them. Final slugs are matched case-insensitively, so 'MyTool' and 'mytool' also collide.

Source

Thrown at python/composio/core/models/_files.py:166

            "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()
    if name == "LOCAL_OUTPUT_FILE_DIRECTORY":
        return get_output_file_directory()
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def get_md5(file: Path) -> str:
    """Calculate MD5 hash of a file for integrity verification.

    Note: MD5 is used here for file integrity checking and deduplication,
    not for cryptographic security. The Composio API requires MD5 hashes
    for file upload verification. For security-critical applications,
    consider using SHA-256 for additional integrity checks.

    Args:
        file: Path to file to hash

    Returns:
        Hexadecimal MD5 hash string
    """
    # `usedforsecurity=False` lets this run on FIPS-mode systems, where
    # `hashlib.md5()` without the flag raises `ValueError: [digital envelope

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Rename one of the colliding custom tool slugs
  2. Remove the duplicate registration (same tool added twice)
  3. Check case-insensitive matches (FOO vs foo) when auditing slugs

Example fix

# before
tools = [CustomTool(slug='SEARCH', ...), CustomTool(slug='search', ...)]
# after
tools = [CustomTool(slug='SEARCH_DOCS', ...), CustomTool(slug='SEARCH_ISSUES', ...)]
Defensive patterns

Strategy: validation

Validate before calling

finals = [final_slug_for(h, tk) for h, tk in all_handles]
keys = [f.upper() for f in finals]
if len(keys) != len(set(keys)):
    dupes = {k for k in keys if keys.count(k) > 1}
    raise ValueError(f'Duplicate final slugs: {dupes}')

Type guard

def no_final_slug_collisions(final_slugs: list[str]) -> bool:
    keys = [s.upper() for s in final_slugs]
    return len(keys) == len(set(keys))

Try / catch

try:
    tools_map = build_custom_tools_map(tools, toolkits)
except ValidationError as e:
    if 'already registered' in str(e):
        # rename duplicates deterministically and retry once
        dedupe_and_rename(tools)
        tools_map = build_custom_tools_map(tools, toolkits)
    else:
        raise

Prevention

When it happens

Trigger: Registering two CustomTool handles (across tools and toolkits) whose computed final slugs match case-insensitively, via build_custom_tools_map or build_custom_tools_map_from_response (e.g. composio.tools.create / use).

Common situations: Registering the same tool twice; tools named the same in two toolkits; casing differences between slugs; merging tool lists from multiple modules.

Related errors


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