bytedance/deer-flow · warning · HTTPException

Skill archives cannot be edited in the artifacts panel

Error message

Skill archives cannot be edited in the artifacts panel

What it means

HTTP 415 from _normalize_editable_artifact_path: paths containing '.skill/' or ending in '.skill' are refused for editing. .skill files are ZIP archives managed by the skills subsystem, not plain text artifacts, so the text editor must not rewrite them (a partial write would corrupt the archive).

Source

Thrown at backend/app/gateway/routers/artifacts.py:73

@asynccontextmanager
async def reserve_artifact_write(request: Request, thread_id: str, *, user_id: str) -> AsyncIterator[None]:
    """Serialize an artifact edit against runs and other thread mutations."""
    run_manager = get_run_manager(request)
    async with run_manager.reserve_thread_operation(
        thread_id,
        kind=ThreadOperationKind.artifact_write,
        user_id=user_id,
    ):
        yield


def _normalize_editable_artifact_path(path: str) -> str:
    stripped = path.lstrip("/")
    if not stripped.startswith(_EDITABLE_OUTPUTS_PREFIX):
        raise HTTPException(status_code=400, detail="Only files in /mnt/user-data/outputs can be edited")
    if ".skill/" in stripped or stripped.endswith(".skill"):
        raise HTTPException(status_code=415, detail="Skill archives cannot be edited in the artifacts panel")
    return f"/{stripped}"


def _load_editable_artifact(actual_path: Path, path: str, expected_sha256: str) -> tuple[bytes, os.stat_result]:
    try:
        file_stat = os.lstat(actual_path)
    except FileNotFoundError:
        raise HTTPException(status_code=404, detail=f"Artifact not found: {path}") from None
    if stat.S_ISLNK(file_stat.st_mode):
        raise HTTPException(status_code=415, detail="Symlinked artifacts cannot be edited")
    if not stat.S_ISREG(file_stat.st_mode):
        raise HTTPException(status_code=400, detail=f"Path is not a file: {path}")
    if file_stat.st_size > MAX_EDITABLE_ARTIFACT_BYTES:
        raise HTTPException(status_code=413, detail="Artifact is too large to edit")

    current = actual_path.read_bytes()
    if len(current) > MAX_EDITABLE_ARTIFACT_BYTES:
        raise HTTPException(status_code=413, detail="Artifact is too large to edit")

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Do not edit .skill archives in the artifact editor — unpack, edit, and repack via the skills API or skill tooling
  2. If the file is genuinely plain text that merely sits inside a '.skill' directory, move/rename it outside the .skill path first
  3. Use GET artifact with the skill-archive branch (it previews members read-only) to inspect contents instead of editing
Defensive patterns

Strategy: validation

Validate before calling

def is_skill_archive_path(path: str) -> bool:
    stripped = path.lstrip("/")
    return ".skill/" in stripped or stripped.endswith(".skill")

Type guard

def is_editable_artifact_path(path: str) -> bool:
    stripped = path.lstrip("/")
    if not stripped.startswith("mnt/user-data/outputs/"):
        return False
    return ".skill/" not in stripped and not stripped.endswith(".skill")

Try / catch

if resp.status_code == 415 and "Skill archives cannot be edited" in resp.text:
    show_readonly_preview(path)  # fall back to GET preview

Prevention

When it happens

Trigger: PUT edit-artifact with a path such as 'mnt/user-data/outputs/packs/my.skill' or 'mnt/user-data/outputs/my.skill/SKILL.md' — any editable-prefix path that is inside or is a skill archive.

Common situations: Users trying to tweak a downloaded/generated skill pack from the artifacts panel, or an agent that saved a .skill bundle into outputs and the UI offers it for editing.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/a776f34c82a8132f. Report an issue: GitHub.