{"record":{"id":"7b083e45d300617f","repo":"langflow-ai/langflow","slug":"per-file-metadata-keys-must-be-non-empty-filename","errorCode":null,"errorMessage":"Per-file metadata keys must be non-empty filename strings.","messagePattern":"Per-file metadata keys must be non-empty filename strings\\.","errorType":"validation","errorClass":"HTTPException","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/utils/kb_metadata.py","lineNumber":129,"sourceCode":"    \"\"\"\n    if not raw:\n        return {}\n    try:\n        decoded = json.loads(raw)\n    except json.JSONDecodeError as exc:\n        msg = f\"Per-file metadata is not valid JSON: {exc.msg}\"\n        raise HTTPException(status_code=422, detail=msg) from exc\n    if not isinstance(decoded, dict):\n        msg = \"Per-file metadata must be a JSON object keyed by filename.\"\n        raise HTTPException(status_code=422, detail=msg)\n    if len(decoded) > KB_METADATA_MAX_KEYS:\n        msg = f\"Per-file metadata exceeds the {KB_METADATA_MAX_KEYS} file limit.\"\n        raise HTTPException(status_code=422, detail=msg)\n    out: dict[str, dict[str, Any]] = {}\n    for filename, file_metadata in decoded.items():\n        if not isinstance(filename, str) or not filename:\n            msg = \"Per-file metadata keys must be non-empty filename strings.\"\n            raise HTTPException(status_code=422, detail=msg)\n        if not isinstance(file_metadata, dict):\n            msg = f\"Per-file metadata for {filename!r} must be a JSON object.\"\n            raise HTTPException(status_code=422, detail=msg)\n        out[filename] = validate_user_metadata(file_metadata)\n    return out\n","sourceCodeStart":111,"sourceCodeEnd":135,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/utils/kb_metadata.py#L111-L135","documentation":"Inside the per_file_metadata loop, each top-level key must be a non-empty string (a filename). JSON keys from json.loads are always strings, so the reachable condition is the empty-string key '' — 422 'Per-file metadata keys must be non-empty filename strings.' After this, each value must be an object (validated by validate_user_metadata), so a non-dict inner value fails errors 151/153/etc. instead.","triggerScenarios":"per_file_metadata JSON containing an empty-string key: {\"\": {\"dept\": \"fin\"}}. Typically produced when the filename variable was empty/None at build time (os.path.basename of a directory path, a blank spreadsheet cell, or an unpopulated form field).","commonSituations":"Path handling bugs: basename('/uploads/') returns ''; iterating rows where the filename column is missing; defaulting an unknown filename to '' instead of skipping; filename lost when a file part is named differently than expected and code falls back to ''.","solutions":["Skip entries with falsy filenames when building the map: {k: v for k, v in per_file.items() if k}.","Fix the source: assert the filename is non-empty when you read the upload part, and log/skip rows missing it.","Match keys exactly to the uploaded files' names (the overrides are looked up per file; an empty key could never match anyway).","Add a client-side guard: all(isinstance(k, str) and k for k in per_file)."],"exampleFix":"# before\nper_file[name] = meta  # name == '' for missing rows -> 422\n\n# after\nif name:\n    per_file[name] = meta\nelse:\n    logger.warning('skipping metadata row without filename')","handlingStrategy":"validation","validationCode":"def clean_per_file_keys(per_file: dict) -> dict:\n    dropped = [k for k in per_file if not (isinstance(k, str) and k)]\n    if dropped:\n        logger.warning('dropping metadata entries with empty filenames: %r', dropped)\n    return {k: v for k, v in per_file.items() if isinstance(k, str) and k}","typeGuard":"def has_valid_filename_keys(per_file) -> bool:\n    return all(isinstance(k, str) and k for k in per_file)","tryCatchPattern":"try:\n    parse_per_file_metadata(raw)\nexcept HTTPException as e:\n    if e.status_code == 422 and 'non-empty filename strings' in e.detail:\n        per_file = clean_per_file_keys(per_file)\n        raw = json.dumps(per_file)\n        parse_per_file_metadata(raw)\n    else:\n        raise","preventionTips":["Guard basename()/filename extraction: skip when the result is empty.","Validate filename columns in source data before building the map.","Keys must exactly match the uploaded file names to ever apply — an empty key is always a bug upstream."],"tags":["knowledge-base","metadata","validation","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}