{"record":{"id":"86e30a9792470dec","repo":"crewAIInc/crewAI","slug":"project-name-name-contains-no-valid-characters-86e30a","errorCode":null,"errorMessage":"Project name '{name}' contains no valid characters for a Python class name","messagePattern":"Project name '(.+?)' contains no valid characters for a Python class name","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"lib/cli/src/crewai_cli/create_crew.py","lineNumber":98,"sourceCode":"    if not folder_name.isidentifier():\n        raise ValueError(\n            f\"Project name '{name}' would generate invalid Python module name '{folder_name}'\"\n        )\n\n    reserved_names = get_reserved_script_names()\n    if folder_name in reserved_names:\n        raise ValueError(\n            f\"Project name '{name}' would generate folder name '{folder_name}' which is reserved. \"\n            f\"Reserved names are: {', '.join(sorted(reserved_names))}. \"\n            \"Please choose a different name.\"\n        )\n\n    class_name = name.replace(\"_\", \" \").replace(\"-\", \" \").title().replace(\" \", \"\")\n\n    class_name = re.sub(r\"[^a-zA-Z0-9_]\", \"\", class_name)\n\n    if not class_name:\n        raise ValueError(\n            f\"Project name '{name}' contains no valid characters for a Python class name\"\n        )\n\n    if class_name[0].isdigit():\n        raise ValueError(\n            f\"Project name '{name}' would generate class name '{class_name}' which cannot start with a digit\"\n        )\n\n    original_name_clean = re.sub(\n        r\"[^a-zA-Z0-9_]\", \"\", name.replace(\"_\", \"\").replace(\"-\", \"\").lower()\n    )\n    if (\n        keyword.iskeyword(original_name_clean)\n        or keyword.iskeyword(class_name)\n        or class_name in (\"True\", \"False\", \"None\")\n    ):\n        raise ValueError(\n            f\"Project name '{name}' would generate class name '{class_name}' which is a reserved Python keyword\"","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/cli/src/crewai_cli/create_crew.py#L80-L116","documentation":"Raised as UnsupportedFileTypeError by crewai-files validators when the file's MIME content_type is not in the supported_formats tuple for its type class (e.g. an ImageConstraints allowing only image/png, image/jpeg). Validation is exact string membership on the content type, so near-misses like image/jpg or application/octet-stream for a real image still fail.","triggerScenarios":"Calling validate_image (validators.py:210-222) with raise_on_error=True when content_type is absent from supported_formats — e.g. a WEBP sent to constraints that only list png/jpeg, or a client that sends the generic application/octet-stream because it guessed the type from the extension. With raise_on_error=False the message is only appended to the errors list.","commonSituations":"Newer formats (webp, avif, heic) not in a conservative supported_formats tuple; clients that don't set Content-Type and default to octet-stream; capitalization or alias mismatches ('image/JPEG', 'image/jpg'); constraints updated between versions to drop a legacy format.","solutions":["Convert the file to a supported format before validation (e.g. re-encode WEBP to PNG/JPEG)","Ensure the upload path sets the correct Content-Type — sniff with mimetypes.guess_type or python-magic instead of trusting the client","Extend the constraints' supported_formats tuple to include the format you actually accept (e.g. add 'image/webp')","Catch UnsupportedFileTypeError and reply with the supported list from the error's fields"],"exampleFix":"# before\nconstraints = ImageConstraints(supported_formats=('image/png', 'image/jpeg'))\nvalidate_image(file, constraints)  # file is image/webp -> UnsupportedFileTypeError\n\n# after\nconstraints = ImageConstraints(supported_formats=('image/png', 'image/jpeg', 'image/webp'))\nvalidate_image(file, constraints)","handlingStrategy":"validation","validationCode":"import mimetypes\n\ndef content_type_supported(path: str, supported: tuple[str, ...]) -> bool:\n    guessed, _ = mimetypes.guess_type(path)\n    return guessed in supported\n\n# before validation, normalize:\n# content_type = content_type.lower().split(';')[0].strip()","typeGuard":null,"tryCatchPattern":"from crewai_files.processing.errors import UnsupportedFileTypeError\n\ntry:\n    validate_image(img, constraints)\nexcept UnsupportedFileTypeError as e:\n    return bad_request(f\"{e.file_name}: type {e.content_type} not allowed; use one of the supported formats\")","preventionTips":["Normalize the content type before validating: lowercase and strip parameters (image/JPEG; charset=... -> image/jpeg)","Sniff the real MIME type with python-magic instead of trusting client-supplied Content-Type or file extensions","Keep the supported_formats tuple in one shared constant used by both client docs and server constraints","Map common aliases (image/jpg -> image/jpeg) at intake so near-misses don't fail"],"tags":["file-upload","validation","mime-type","crewai-files"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}