bytedance/deer-flow · error · HTTPException
Artifact not found: {path}
Error message
Artifact not found: {path} What it means
HTTP 404 from _load_editable_artifact: os.lstat on the resolved actual path raised FileNotFoundError, meaning the artifact existed in metadata (the client had a path and sha256) but no longer exists on disk at edit-save time. Uses lstat specifically so a dangling symlink also lands here as absent.
Source
Thrown at backend/app/gateway/routers/artifacts.py:81
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")
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()View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Re-fetch the artifact list (GET artifacts) to confirm the file still exists; if gone, discard the local edit buffer
- If the file should exist, check whether the producing run re-executed and wrote it under a different name/path
- Handle 404 by reopening the editor rather than retrying the same PUT
Defensive patterns
Strategy: validation
Validate before calling
# Confirm the file still exists before attempting the save
arts = await client.get(f"/api/threads/{tid}/artifacts")
existing = {a["path"] for a in arts.json().get("artifacts", [])}
if path not in existing:
raise FileNotFoundError(f"{path} disappeared; reopen editor") Try / catch
try:
await client.put(EDIT_URL, json=payload)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
# file vanished since open — reload, do not retry blindly
refresh_artifact_list()
else:
raise Prevention
- Refresh artifact listings when a run finishes before allowing edits
- Disable the save button for artifacts that no longer appear in the listing
- Prefer reopening the file over retrying a 404 save
When it happens
Trigger: PUT edit-artifact for a file deleted since it was opened — the sandbox removed it, the run cleaned up outputs, another editor/session deleted it, or the thread's output dir was recreated.
Common situations: Editing in one tab while a re-run regenerates or removes outputs, stale editor sessions after a thread run finished and cleaned up, or file deleted by the agent mid-conversation.
Related errors
- Skill file not found: {skill_file_path}
- File '{internal_path}' not found in skill archive
- HTTP ${response.status}: ${response.statusText}
- Agent '{name}' not found
- Only files in /mnt/user-data/outputs can be edited
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/65bb358e50802978.
Report an issue: GitHub.