{"record":{"id":"4746b7068e0178e8","repo":"Comfy-Org/ComfyUI","slug":"invalid-body-4746b7","errorCode":"INVALID_BODY","errorMessage":"tags are required for new asset uploads","messagePattern":"tags are required for new asset uploads","errorType":"http","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"app/assets/services/ingest.py","lineNumber":521,"sourceCode":"        result = _register_existing_asset(\n            asset_hash=asset_hash,\n            name=display_name,\n            user_metadata=user_metadata or {},\n            tags=tags or [],\n            tag_origin=\"manual\",\n            owner_id=owner_id,\n            mime_type=mime_type,\n            preview_id=preview_id,\n        )\n        return UploadResult(\n            ref=result.ref,\n            asset=result.asset,\n            tags=result.tags,\n            created_new=False,\n        )\n\n    if not tags:\n        raise ValueError(\"tags are required for new asset uploads\")\n    base_dir, subdirs = resolve_destination_from_tags(tags)\n    dest_dir = os.path.join(base_dir, *subdirs) if subdirs else base_dir\n    os.makedirs(dest_dir, exist_ok=True)\n\n    src_for_ext = (client_filename or name or \"\").strip()\n    _ext = os.path.splitext(os.path.basename(src_for_ext))[1] if src_for_ext else \"\"\n    ext = _ext if 0 < len(_ext) <= 16 else \"\"\n    hashed_basename = f\"{digest}{ext}\"\n    dest_abs = os.path.abspath(os.path.join(dest_dir, hashed_basename))\n    validate_path_within_base(dest_abs, base_dir)\n\n    content_type = mime_type or (\n        mimetypes.guess_type(os.path.basename(src_for_ext), strict=False)[0]\n        or mimetypes.guess_type(hashed_basename, strict=False)[0]\n        or \"application/octet-stream\"\n    )\n\n    try:","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/app/assets/services/ingest.py#L503-L539","documentation":"Raised by the upload ingest path when the uploaded content is new (no existing asset with the same hash) and the request supplies no tags. For brand-new assets, tags are mandatory because they determine the write destination (the input/models/output role routing in resolve_destination_from_tags); known content short-circuits to reference-only creation and does not need them. Raised as ValueError, surfaced as INVALID_BODY.","triggerScenarios":"POST /api/assets/upload with a file whose BLAKE3 hash is not already in the database and with an empty/missing tags list.","commonSituations":"Client sends tags only conditionally (e.g. only on retry); a bug that drops the tags form field; a caller assuming the server will default to the input directory.","solutions":["Include at least one destination-role tag ('input', 'models', or 'output') with every new-asset upload — practically send tags on every upload.","Check the error path: if your flow intentionally re-uploads known content without tags, ensure the hash matches so the reference-only path applies.","Add a client-side check that refuses to submit an upload with an empty tag list."],"exampleFix":"# before\nfiles = {'file': open(path,'rb')}  # no tags field\n\n# after\nfiles = {'file': open(path,'rb')}\ndata = {'tags': ['input', 'user:alice']}","handlingStrategy":"validation","validationCode":"def can_upload(tags: list[str]) -> bool:\n    return bool(tags) and sum(t in {'input','models','output'} for t in tags) == 1","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send a tags field with uploads; empty tags only ever works for already-known content","Include exactly one destination role tag up front","Validate the tag list in the client UI before enabling submit"],"tags":["upload","tags","validation","api-assets"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}