bytedance/deer-flow · error · HTTPException
File '{internal_path}' not found in skill archive
Error message
File '{internal_path}' not found in skill archive What it means
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.
Source
Thrown at backend/app/gateway/routers/artifacts.py:282
return None
def _load_skill_archive_member(actual_skill_path: Path, skill_file_path: str, internal_path: str) -> tuple[bytes, str | None]:
"""Worker-thread body for the ``.skill`` branch of ``get_artifact``.
The ``exists`` / ``is_file`` probes, the ZIP open+extract, and the MIME
sniff (``mimetypes`` lazily stats the system MIME database on first use) are
blocking filesystem IO and must stay off the event loop. Raised
``HTTPException``s propagate through ``asyncio.to_thread`` unchanged,
preserving status codes.
"""
if not actual_skill_path.exists():
raise HTTPException(status_code=404, detail=f"Skill file not found: {skill_file_path}")
if not actual_skill_path.is_file():
raise HTTPException(status_code=400, detail=f"Path is not a file: {skill_file_path}")
content = _extract_file_from_skill_archive(actual_skill_path, internal_path)
if content is None:
raise HTTPException(status_code=404, detail=f"File '{internal_path}' not found in skill archive")
mime_type, _ = mimetypes.guess_type(internal_path)
return content, mime_type
def _read_artifact_payload(actual_path: Path, path: str, download: bool) -> tuple[str, str | None]:
"""Worker-thread body for the regular branch of ``get_artifact``.
Stat probes and MIME sniffing (``mimetypes`` lazily stats the system MIME
database on first use) are blocking filesystem IO. Returns a
``(kind, mime_type)`` plan the handler turns into a streamed
``FileResponse``. Inline text and binary previews both use FileResponse so
clients can request a bounded byte range instead of buffering a whole file.
"""
if not actual_path.exists():
raise HTTPException(status_code=404, detail=f"Artifact not found: {path}")
if not actual_path.is_file():
raise HTTPException(status_code=400, detail=f"Path is not a file: {path}")
mime_type, _ = mimetypes.guess_type(actual_path)View on GitHub (pinned to 1dd6ba1acb)
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
Example fix
# before internal = "SKILL.md" # pack actually has 'my-skill/SKILL.md' → 404 # after internal = "my-skill/SKILL.md" # exact member path from zipfile -l
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def archive_has_member(zip_path: str, internal_path: str) -> bool:
with zipfile.ZipFile(zip_path) as z:
names = set(z.namelist())
return internal_path in names Try / catch
if resp.status_code == 404 and "not found in skill archive" in resp.text:
members = list_zip_members(zip_path) # show what actually exists
offer_member_picker(members) Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Skill file not found: {skill_file_path}
- Skill archives cannot be edited in the artifacts panel
- Artifact not found: {path}
- Skill archive member is too large to preview
- Path is not a file: {skill_file_path}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/0abd69cdcd96c97c.
Report an issue: GitHub.