ComposioHQ/composio · error · ErrorUploadingFile
Failed to upload to S3: {_sanitize_url_for_logging(url)}. Er
Error message
Failed to upload to S3: {_sanitize_url_for_logging(url)}. Error: {type(e).__name__} What it means
One original slug maps to multiple different final slugs, which would make tool resolution ambiguous. This happens when the same custom tool slug appears in contexts that produce different final slugs (e.g. the same bare slug both inside and outside a prefixed toolkit).
Source
Thrown at python/composio/core/models/_files.py:223
Routing every presigned PUT through one helper keeps the file and bytes
upload paths from drifting apart again, mirroring ``uploadFileToS3`` in
the TypeScript SDK, which funnels path, URL, and File inputs through a
single uploader.
Raises:
ErrorUploadingFile: On transport failure or a non-200 response,
including the HTTP status when one was received.
"""
try:
response = safe_request(
"PUT",
url,
data=data,
headers={"Content-Type": mimetype},
timeout=(_CONNECT_TIMEOUT, _READ_TIMEOUT),
)
except requests.exceptions.RequestException as e:
raise ErrorUploadingFile(
"Failed to upload to S3: "
f"{_sanitize_url_for_logging(url)}. Error: {type(e).__name__}"
) from e
if response.status_code != 200:
raise ErrorUploadingFile(
f"Failed to upload to S3. Status: {response.status_code}. "
"This may indicate an expired presigned URL or permission issue."
)
def upload(url: str, file: Path, mimetype: t.Optional[str] = None) -> bool:
"""Upload file to presigned S3 URL.
Args:
url: Presigned S3 upload URL
file: Path to file to upload
mimetype: Content type to send with the upload. Defaults to the type
guessed from ``file``. This must match the ``mimetype`` theView on GitHub (pinned to 64b1b85502)
Solutions
- Remove the standalone copy of the tool that is also inside the toolkit
- Rename one instance so each original slug appears in only one context
- Audit how toolkits are combined before calling create/use
Example fix
# before tools = [CustomTool(slug='FETCH', ...)] toolkit = Toolkit(slug='gh', tools=[CustomTool(slug='FETCH', ...)]) # after tools = [] toolkit = Toolkit(slug='gh', tools=[CustomTool(slug='FETCH', ...)])
Defensive patterns
Strategy: validation
Validate before calling
standalone = {h.slug.upper() for h in tools}
in_toolkits = {h.slug.upper() for tk in toolkits or [] for h in tk.tools}
overlap = standalone & in_toolkits
if overlap:
raise ValueError(f'Slugs registered both standalone and in toolkits: {overlap}') Type guard
def each_slug_single_context(tools: list, toolkits: list) -> bool:
standalone = {h.slug.upper() for h in tools}
in_tk = {h.slug.upper() for tk in toolkits for h in tk.tools}
return not (standalone & in_tk) Try / catch
try:
tools_map = build_custom_tools_map(tools, toolkits)
except ValidationError as e:
if 'maps to multiple final slugs' in str(e):
tools = [h for h in tools if h.slug.upper() not in toolkit_slugs(toolkits)]
tools_map = build_custom_tools_map(tools, toolkits)
else:
raise Prevention
- Register each tool in exactly one place: standalone OR in a toolkit
- When moving tools into toolkits, delete the old standalone registrations
When it happens
Trigger: Registering a custom tool with slug X standalone and the same slug X inside a toolkit whose prefix produces a different final slug, so by_original_slug[X] already has an entry with a different final_slug, during build_custom_tools_map(_from_response).
Common situations: Mixing standalone tools and toolkit tools that share slugs; refactoring tools into toolkits while leaving standalone copies registered; dynamically composed tool lists.
Related errors
- Cache directory {directory} is not writable please provide a
- module {__name__!r} has no attribute {name!r}
- 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/dbe79ffbcac40c42.
Report an issue: GitHub.