{"record":{"id":"c5ac8766a97a14ca","repo":"bytedance/deer-flow","slug":"path-is-not-a-file-skill-file-path","errorCode":null,"errorMessage":"Path is not a file: {skill_file_path}","messagePattern":"Path is not a file: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/app/gateway/routers/artifacts.py","lineNumber":279,"sourceCode":"            # Not found\n            return None\n    except (zipfile.BadZipFile, KeyError):\n        return None\n\n\ndef _load_skill_archive_member(actual_skill_path: Path, skill_file_path: str, internal_path: str) -> tuple[bytes, str | None]:\n    \"\"\"Worker-thread body for the ``.skill`` branch of ``get_artifact``.\n\n    The ``exists`` / ``is_file`` probes, the ZIP open+extract, and the MIME\n    sniff (``mimetypes`` lazily stats the system MIME database on first use) are\n    blocking filesystem IO and must stay off the event loop. Raised\n    ``HTTPException``s propagate through ``asyncio.to_thread`` unchanged,\n    preserving status codes.\n    \"\"\"\n    if not actual_skill_path.exists():\n        raise HTTPException(status_code=404, detail=f\"Skill file not found: {skill_file_path}\")\n    if not actual_skill_path.is_file():\n        raise HTTPException(status_code=400, detail=f\"Path is not a file: {skill_file_path}\")\n    content = _extract_file_from_skill_archive(actual_skill_path, internal_path)\n    if content is None:\n        raise HTTPException(status_code=404, detail=f\"File '{internal_path}' not found in skill archive\")\n    mime_type, _ = mimetypes.guess_type(internal_path)\n    return content, mime_type\n\n\ndef _read_artifact_payload(actual_path: Path, path: str, download: bool) -> tuple[str, str | None]:\n    \"\"\"Worker-thread body for the regular branch of ``get_artifact``.\n\n    Stat probes and MIME sniffing (``mimetypes`` lazily stats the system MIME\n    database on first use) are blocking filesystem IO. Returns a\n    ``(kind, mime_type)`` plan the handler turns into a streamed\n    ``FileResponse``. Inline text and binary previews both use FileResponse so\n    clients can request a bounded byte range instead of buffering a whole file.\n    \"\"\"\n    if not actual_path.exists():\n        raise HTTPException(status_code=404, detail=f\"Artifact not found: {path}\")","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/artifacts.py#L261-L297","documentation":"HTTP 400 from _load_skill_archive_member: the .skill path exists but is not a regular file (actual_skill_path.is_file() is False) — typically a directory named like a skill archive. The ZIP open would fail confusingly, so it is rejected up front with a clear status.","triggerScenarios":"GET artifact for a path ending in .skill that is actually a directory (e.g. an unpacked skill folder saved to outputs with the same name), or a special file at that path.","commonSituations":"Agents unpacking skill packs into outputs and leaving a directory where the archive used to be, or unzipped leftovers from manual debugging.","solutions":["Point the request at the packed .skill file, not the unpacked directory","If only the directory form exists, re-zip it into a .skill archive first","Filter non-file .skill entries out of preview UIs"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_skill_archive_file(p: str) -> bool:\n    path = Path(p)\n    return path.exists() and path.is_file()","typeGuard":null,"tryCatchPattern":"if resp.status_code == 400 and \"Path is not a file\" in resp.text:\n    offer_unpacked_tree_preview(path)  # directory form","preventionTips":["Keep packed .skill archives and unpacked directories under distinct names in outputs","Client-side is_file checks before preview avoid the round trip","Note is_file() here follows symlinks, unlike the editable path's lstat check"],"tags":["api","artifacts","skills","path-validation","http-400"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}