ComposioHQ/composio · error · ErrorUploadingFile
Failed to upload to S3. Status: {response.status_code}. This
Error message
Failed to upload to S3. Status: {response.status_code}. This may indicate an expired presigned URL or permission issue. What it means
While building the custom tools map from a response, two top-level custom tool handles share the same slug (compared case-insensitively via .upper()). Every custom tool must have a unique slug across all custom tools and toolkits.
Source
Thrown at python/composio/core/models/_files.py:228
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`` the
presigned URL was requested with, otherwise S3 rejects the PUT
with ``403 SignatureDoesNotMatch`` when the signature covers the
content type.
Returns:View on GitHub (pinned to 64b1b85502)
Solutions
- Dedupe the tools list before passing it (by slug.upper())
- Rename one of the duplicate-slug tools
Example fix
# before tools = [t1, t2] # both slug='SEND' # after seen = set() tools = [t for t in [t1, t2] if not (t.slug.upper() in seen or seen.add(t.slug.upper()))]
Defensive patterns
Strategy: validation
Validate before calling
keys = [h.slug.upper() for h in tools]
if len(keys) != len(set(keys)):
raise ValueError(f'Duplicate tool slugs: {sorted({k for k in keys if keys.count(k) > 1})}')
result = build_custom_tools_map_from_response(tools, toolkits) Type guard
def unique_tool_slugs(tools: list) -> bool:
keys = [h.slug.upper() for h in tools]
return len(keys) == len(set(keys)) Try / catch
try:
m = build_custom_tools_map_from_response(tools, toolkits)
except ValidationError as e:
if 'unique slug' in str(e):
seen, deduped = set(), []
for h in tools:
k = h.slug.upper()
if k not in seen:
seen.add(k)
deduped.append(h)
m = build_custom_tools_map_from_response(deduped, toolkits)
else:
raise Prevention
- Dedupe tool lists by slug.upper() before merging lists from modules
- Standardize on one casing convention for slugs
When it happens
Trigger: Passing a tools list to build_custom_tools_map_from_response (via create/use) containing two CustomTool handles whose slug.upper() matches.
Common situations: Concatenating tool lists that each define the same tool; copy-pasting tool definitions; case-variant slugs like 'Send_Email' and 'send_email'.
Related errors
- module {__name__!r} has no attribute {name!r}
- Cache directory {directory} is not writable please provide a
- Failed to upload to S3: {_sanitize_url_for_logging(url)}. Er
- Request timed out fetching URL: {_sanitize_url_for_logging(u
- Could not determine a home directory to store the Composio c
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/0789e51e4808bdc9.
Report an issue: GitHub.