ComposioHQ/composio · error · UnsafePathComponentError
Refusing to write {label} longer than {MAX_COMPONENT_LENGTH}
Error message
Refusing to write {label} longer than {MAX_COMPONENT_LENGTH} bytes: {basename[:32]!r}... ({encoded_length} bytes) What it means
safe_basename rejects filenames whose filesystem-encoded length exceeds MAX_COMPONENT_LENGTH (128) bytes. os.fsencode measures real bytes, so multi-byte UTF-8 names hit the cap sooner than their character count suggests, keeping writes safely under common 255-byte filename limits.
Source
Thrown at python/composio/utils/safe_path.py:198
if any(ord(char) < 32 or char in '<>:"|?*' for char in raw_basename):
raise UnsafePathComponentError(
f"Refusing to write {label} containing characters reserved by "
f"Windows: {name!r}"
)
if raw_basename.endswith((" ", ".")):
raise UnsafePathComponentError(
f"Refusing to write {label} ending in a space or dot: {name!r}"
)
basename = raw_basename.strip()
try:
encoded_length = len(os.fsencode(basename))
except UnicodeEncodeError as e:
raise UnsafePathComponentError(
f"Refusing to write {label} containing invalid Unicode: {name!r}"
) from e
if encoded_length > MAX_COMPONENT_LENGTH:
raise UnsafePathComponentError(
f"Refusing to write {label} longer than {MAX_COMPONENT_LENGTH} bytes: "
f"{basename[:32]!r}... ({encoded_length} bytes)"
)
# Compare everything before the first dot: on Windows `NUL.tar.gz` opens
# the null device just as `NUL` does, so any number of extensions provides
# no protection.
device_name = basename.split(".", 1)[0].rstrip(" ").upper()
if device_name in WINDOWS_RESERVED_NAMES:
raise UnsafePathComponentError(
f"Refusing to write {label} that is a reserved device name: {name!r}"
)
return basename
def resolve_root(root: t.Union[str, Path]) -> Path:
"""Normalize a trusted root to an absolute, symlink-resolved path.
Every containment check must derive its anchor through this one function.View on GitHub (pinned to 64b1b85502)
Solutions
- Truncate the filename to a byte budget preserving the extension
- Replace long names with a short hash and keep the original in metadata
Example fix
# before
secure_basename_join(base, long_name)
# after
stem, _, ext = long_name.rpartition('.')
budget = 120 - len(ext.encode()) - 1
long_name = stem.encode('utf-8')[:budget].decode('utf-8', 'ignore') + '.' + ext
secure_basename_join(base, long_name) Defensive patterns
Strategy: validation
Validate before calling
import os
MAX = 128
def within_byte_budget(v):
try: return len(os.fsencode(v)) <= MAX
except UnicodeEncodeError: return False Type guard
import os
def is_short_filename(v: str) -> bool:
try:
return len(os.fsencode(v)) <= 128
except UnicodeEncodeError:
return False Try / catch
from composio.exceptions import UnsafePathComponentError
try:
p = secure_basename_join(base, name)
except UnsafePathComponentError:
stem, _, ext = name.rpartition('.')
p = secure_basename_join(base, stem[:64] + ('.' + ext if ext else '')) Prevention
- Measure filename length in bytes, not characters
- Prefer short hash-based filenames; keep original names in metadata
When it happens
Trigger: secure_basename_join(base, name) where len(os.fsencode(name)) > 128 — long descriptive filenames, CJK filenames (3 bytes/char), or hash-based names.
Common situations: Generated filenames embedding full titles or URLs; Unicode-heavy locales inflating byte length; download names composed from several API fields.
Related errors
- Could not determine a home directory to store the Composio c
- Cache directory {directory} is not writable please provide a
- module {__name__!r} has no attribute {name!r}
- Failed to upload to S3: {_sanitize_url_for_logging(url)}. Er
- Failed to upload to S3. Status: {response.status_code}. This
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/23e27fb41e2e00b2.
Report an issue: GitHub.