ComposioHQ/composio · error · ErrorUploadingFile

URL returned redirect to {_sanitize_url_for_logging(location

Error message

URL returned redirect to {_sanitize_url_for_logging(location)}. Please provide a direct URL to the file.

What it means

One or more slugs in the top-level preload.tools list belong to custom tools. Custom tools cannot be preloaded via preload.tools; they must be exposed by setting preload=True on the SDK custom tool or custom toolkit definition.

Source

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

        response = safe_get(
            url,
            stream=True,  # Enable streaming for size limiting
            timeout=(_CONNECT_TIMEOUT, _READ_TIMEOUT),
        )
    except requests.exceptions.Timeout:
        raise ErrorUploadingFile(
            f"Request timed out fetching URL: {_sanitize_url_for_logging(url)}"
        )
    except requests.exceptions.RequestException as e:
        raise ErrorUploadingFile(
            f"Failed to fetch file from URL: {_sanitize_url_for_logging(url)}. Error: {e}"
        )

    # Reject redirects - require direct URL to resource
    if response.status_code in (301, 302, 303, 307, 308):
        location = response.headers.get("Location", "unknown")
        response.close()
        raise ErrorUploadingFile(
            f"URL returned redirect to {_sanitize_url_for_logging(location)}. "
            f"Please provide a direct URL to the file."
        )

    # Check for successful response
    if not response.ok:
        response.close()
        raise ErrorUploadingFile(
            f"Failed to fetch file from URL: {_sanitize_url_for_logging(url)}. "
            f"Status: {response.status_code}"
        )

    # Check Content-Length header first (early abort for oversized files).
    # The header is a hint from the remote server: `parse_content_length`
    # returns None for anything untrustworthy, and the streaming guard below
    # is the authoritative limit.
    content_length = parse_content_length(response.headers.get("Content-Length"))
    if content_length is not None and content_length > max_size:

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Remove custom tool slugs from preload.tools and set preload=True on those CustomTool/Toolkit definitions
  2. Filter the preload list to only Composio app tool slugs
  3. If you want everything including custom tools, set preload=True on the definitions and use 'all' only for app tools

Example fix

# before
tool = CustomTool(slug='MY_TOOL', preload=False, ...)
composio.use(custom_tools=[tool], preload_tools=['MY_TOOL'])
# after
tool = CustomTool(slug='MY_TOOL', preload=True, ...)
composio.use(custom_tools=[tool])
Defensive patterns

Strategy: validation

Validate before calling

custom_finals = {f.upper() for f in custom_tools_map.by_final_slug} if custom_tools_map else set()
safe = [s for s in preload_tools if s.upper() not in custom_finals]
composio.use(custom_tools=tools, preload_tools=safe)

Type guard

def contains_only_app_tools(preload_tools: list, custom_map) -> bool:
    custom_keys = ({*custom_map.by_final_slug} | {*getattr(custom_map, 'by_original_slug', {})}) if custom_map else set()
    return all(s.upper() not in {k.upper() for k in custom_keys} for s in preload_tools)

Try / catch

try:
    composio.use(custom_tools=tools, preload_tools=preload_tools)
except ValidationError as e:
    if 'not supported in preload.tools' in str(e):
        bad = set(str(e).split(': ')[1].split('.')[0].split(', '))
        composio.use(custom_tools=tools, preload_tools=[s for s in preload_tools if s not in bad])
    else:
        raise

Prevention

When it happens

Trigger: Passing a preload list containing a slug that matches (normalized) a custom tool in custom_tools_map.by_final_slug / by_original_slug during _prepare_inline_custom_tools, e.g. composio.use(preload_tools=['MY_CUSTOM_TOOL']).

Common situations: Treating custom tool slugs like Composio app tool slugs; migrating config where all tools were preloaded from one list; dynamic preload lists built from mixed sources.

Related errors


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