{"record":{"id":"e506bb35581814fc","repo":"bytedance/deer-flow","slug":"symlinked-artifacts-cannot-be-edited","errorCode":null,"errorMessage":"Symlinked artifacts cannot be edited","messagePattern":"Symlinked artifacts cannot be edited","errorType":"http","errorClass":"HTTPException","httpStatus":415,"severity":"warning","filePath":"backend/app/gateway/routers/artifacts.py","lineNumber":83,"sourceCode":"        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\")\n    if b\"\\x00\" in current:\n        raise HTTPException(status_code=415, detail=\"Binary artifacts cannot be edited\")\n    try:\n        current.decode(\"utf-8\")\n    except UnicodeDecodeError:\n        raise HTTPException(status_code=415, detail=\"Only UTF-8 text artifacts can be edited\") from None\n\n    current_sha256 = hashlib.sha256(current).hexdigest()\n    if current_sha256 != expected_sha256:\n        raise HTTPException(status_code=412, detail=\"Artifact changed since it was opened\")","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/artifacts.py#L65-L101","documentation":"HTTP 415 from _load_editable_artifact: os.lstat shows S_ISLNK, i.e. the artifact path is a symbolic link. The atomic replace strategy (mkstemp + rename in the same directory) would replace the link itself, not the target, silently changing semantics — so symlinks are excluded from editing.","triggerScenarios":"PUT edit-artifact where the outputs path is a symlink (agent created ln -s to share a file across output paths, or an operator symlinked outputs into the directory).","commonSituations":"Agents using symlinks to deduplicate large outputs, deployments where /mnt/user-data/outputs is itself reached through links, or attempts to edit a file whose canonical content lives outside outputs via a link.","solutions":["Replace the symlink with the real file (cp -L) if editing is required, then retry","Edit the canonical target through its own outputs path if it is itself under outputs","Keep symlinked artifacts read-only in UIs and surface the 415 detail to the user"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import os, stat\n\ndef is_editable_file(actual_path: str) -> bool:\n    try:\n        st = os.lstat(actual_path)\n    except OSError:\n        return False\n    return not stat.S_ISLNK(st.st_mode) and stat.S_ISREG(st.st_mode)","typeGuard":null,"tryCatchPattern":"if resp.status_code == 415 and \"Symlinked\" in resp.text:\n    # resolve or materialize the real file, then re-open editor\n    materialize_real_file(path)","preventionTips":["Avoid creating symlinks inside outputs if the files need in-place editing","lstat-based client checks can pre-detect links before offering Edit","Treat 415 symlink as a UI concern: show read-only view"],"tags":["api","artifacts","symlink","http-415","filesystem"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}