bytedance/deer-flow · warning · HTTPException
Symlinked artifacts cannot be edited
Error message
Symlinked artifacts cannot be edited
What it means
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.
Source
Thrown at backend/app/gateway/routers/artifacts.py:83
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")
if b"\x00" in current:
raise HTTPException(status_code=415, detail="Binary artifacts cannot be edited")
try:
current.decode("utf-8")
except UnicodeDecodeError:
raise HTTPException(status_code=415, detail="Only UTF-8 text artifacts can be edited") from None
current_sha256 = hashlib.sha256(current).hexdigest()
if current_sha256 != expected_sha256:
raise HTTPException(status_code=412, detail="Artifact changed since it was opened")View on GitHub (pinned to 1dd6ba1acb)
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
Defensive patterns
Strategy: validation
Validate before calling
import os, stat
def is_editable_file(actual_path: str) -> bool:
try:
st = os.lstat(actual_path)
except OSError:
return False
return not stat.S_ISLNK(st.st_mode) and stat.S_ISREG(st.st_mode) Try / catch
if resp.status_code == 415 and "Symlinked" in resp.text:
# resolve or materialize the real file, then re-open editor
materialize_real_file(path) Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Skill archives cannot be edited in the artifacts panel
- Path is not a file: {path}
- Binary artifacts cannot be edited
- Only UTF-8 text artifacts can be edited
- Binary content cannot be saved as an artifact
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/e506bb35581814fc.
Report an issue: GitHub.