{"record":{"id":"0abd69cdcd96c97c","repo":"bytedance/deer-flow","slug":"file-internal-path-not-found-in-skill-archive","errorCode":null,"errorMessage":"File '{internal_path}' not found in skill archive","messagePattern":"File '(.+?)' not found in skill archive","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/app/gateway/routers/artifacts.py","lineNumber":282,"sourceCode":"        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}\")\n    if not actual_path.is_file():\n        raise HTTPException(status_code=400, detail=f\"Path is not a file: {path}\")\n    mime_type, _ = mimetypes.guess_type(actual_path)","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/artifacts.py#L264-L300","documentation":"HTTP 404 from _load_skill_archive_member: the .skill ZIP opened fine but _extract_file_from_skill_archive found no member matching the requested internal_path (returned None). The archive exists; the file inside it (e.g. 'SKILL.md' or a nested asset) does not.","triggerScenarios":"GET artifact with a .skill#internal-path where internal_path has wrong casing, a missing parent folder inside the archive, or references a member the pack simply does not ship (README vs SKILL.md confusion).","commonSituations":"UIs hardcoding 'SKILL.md' for packs that ship different layouts, hand-built archives with unexpected roots (extra top-level folder), or version changes in skill-pack structure.","solutions":["List the archive's members (python -m zipfile -l pack.skill) and use the exact name, including any top-level folder","Rebuild the archive so the expected file sits at the expected internal path","Make pack structure conform to the skill spec so standard members like SKILL.md always exist"],"exampleFix":"# before\ninternal = \"SKILL.md\"           # pack actually has 'my-skill/SKILL.md' → 404\n\n# after\ninternal = \"my-skill/SKILL.md\"  # exact member path from zipfile -l","handlingStrategy":"validation","validationCode":"import zipfile\n\ndef archive_has_member(zip_path: str, internal_path: str) -> bool:\n    with zipfile.ZipFile(zip_path) as z:\n        names = set(z.namelist())\n    return internal_path in names","typeGuard":null,"tryCatchPattern":"if resp.status_code == 404 and \"not found in skill archive\" in resp.text:\n    members = list_zip_members(zip_path)   # show what actually exists\n    offer_member_picker(members)","preventionTips":["Enumerate members before deep-linking into archives; do not assume layout","Normalize skill-pack builds so SKILL.md sits at the archive root","Case matters: ZIP member names are case-sensitive"],"tags":["api","artifacts","skills","zip","not-found","http-404"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}