{"record":{"id":"a776f34c82a8132f","repo":"bytedance/deer-flow","slug":"skill-archives-cannot-be-edited-in-the-artifacts-p","errorCode":null,"errorMessage":"Skill archives cannot be edited in the artifacts panel","messagePattern":"Skill archives cannot be edited in the artifacts panel","errorType":"http","errorClass":"HTTPException","httpStatus":415,"severity":"warning","filePath":"backend/app/gateway/routers/artifacts.py","lineNumber":73,"sourceCode":"\n@asynccontextmanager\nasync def reserve_artifact_write(request: Request, thread_id: str, *, user_id: str) -> AsyncIterator[None]:\n    \"\"\"Serialize an artifact edit against runs and other thread mutations.\"\"\"\n    run_manager = get_run_manager(request)\n    async with run_manager.reserve_thread_operation(\n        thread_id,\n        kind=ThreadOperationKind.artifact_write,\n        user_id=user_id,\n    ):\n        yield\n\n\ndef _normalize_editable_artifact_path(path: str) -> str:\n    stripped = path.lstrip(\"/\")\n    if not stripped.startswith(_EDITABLE_OUTPUTS_PREFIX):\n        raise HTTPException(status_code=400, detail=\"Only files in /mnt/user-data/outputs can be edited\")\n    if \".skill/\" in stripped or stripped.endswith(\".skill\"):\n        raise HTTPException(status_code=415, detail=\"Skill archives cannot be edited in the artifacts panel\")\n    return f\"/{stripped}\"\n\n\ndef _load_editable_artifact(actual_path: Path, path: str, expected_sha256: str) -> tuple[bytes, os.stat_result]:\n    try:\n        file_stat = os.lstat(actual_path)\n    except FileNotFoundError:\n        raise HTTPException(status_code=404, detail=f\"Artifact not found: {path}\") from None\n    if stat.S_ISLNK(file_stat.st_mode):\n        raise HTTPException(status_code=415, detail=\"Symlinked artifacts cannot be edited\")\n    if not stat.S_ISREG(file_stat.st_mode):\n        raise HTTPException(status_code=400, detail=f\"Path is not a file: {path}\")\n    if file_stat.st_size > MAX_EDITABLE_ARTIFACT_BYTES:\n        raise HTTPException(status_code=413, detail=\"Artifact is too large to edit\")\n\n    current = actual_path.read_bytes()\n    if len(current) > MAX_EDITABLE_ARTIFACT_BYTES:\n        raise HTTPException(status_code=413, detail=\"Artifact is too large to edit\")","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/artifacts.py#L55-L91","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Do not edit .skill archives in the artifact editor — unpack, edit, and repack via the skills API or skill tooling","If the file is genuinely plain text that merely sits inside a '.skill' directory, move/rename it outside the .skill path first","Use GET artifact with the skill-archive branch (it previews members read-only) to inspect contents instead of editing"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"def is_skill_archive_path(path: str) -> bool:\n    stripped = path.lstrip(\"/\")\n    return \".skill/\" in stripped or stripped.endswith(\".skill\")","typeGuard":"def is_editable_artifact_path(path: str) -> bool:\n    stripped = path.lstrip(\"/\")\n    if not stripped.startswith(\"mnt/user-data/outputs/\"):\n        return False\n    return \".skill/\" not in stripped and not stripped.endswith(\".skill\")","tryCatchPattern":"if resp.status_code == 415 and \"Skill archives cannot be edited\" in resp.text:\n    show_readonly_preview(path)  # fall back to GET preview","preventionTips":["Hide .skill entries behind a read-only preview in artifact UIs","Treat .skill as an opaque package: modify via skills endpoints, never byte-level editors","When generating skill packs into outputs, name them clearly so users understand they are archives"],"tags":["api","artifacts","skills","http-415","zip"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}