bytedance/deer-flow · error · HTTPException

Binary content cannot be saved as an artifact

Error message

Binary content cannot be saved as an artifact

What it means

HTTP 415 from _encode_artifact_update: the incoming content string encodes to UTF-8 bytes containing a NUL byte. The save path mirrors the load-side binary check: the artifacts editor must remain text-only end to end, so binary payloads (even those smuggled inside a JSON string) are rejected before any disk write.

Source

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

    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")
    return current, file_stat


def _encode_artifact_update(content: str) -> bytes:
    encoded = content.encode("utf-8")
    if len(encoded) > MAX_EDITABLE_ARTIFACT_BYTES:
        raise HTTPException(status_code=413, detail="Artifact is too large to edit")
    if b"\x00" in encoded:
        raise HTTPException(status_code=415, detail="Binary content cannot be saved as an artifact")
    return encoded


def _replace_artifact_atomically(actual_path: Path, content: bytes, file_stat: os.stat_result) -> None:
    temp_fd, temp_path_str = tempfile.mkstemp(prefix=_ARTIFACT_EDIT_TEMP_PREFIX, dir=actual_path.parent)
    temp_path = Path(temp_path_str)
    try:
        # Preserve ownership where possible and keep replacement permissions
        # scoped to the owner/group. The shared outputs directory allows a
        # mounted sandbox to reach the file without making it world-writable.
        if hasattr(os, "fchown"):
            try:
                os.fchown(temp_fd, file_stat.st_uid, file_stat.st_gid)
            except OSError:
                logger.debug("Could not preserve artifact ownership: %s", actual_path, exc_info=True)
        # Windows has no fchmod and uses ACLs rather than POSIX mode bits.
        # Keep the mkstemp permissions there; retain the existing POSIX
        # behavior on platforms that expose descriptor-based chmod.

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Strip or reject NUL characters in content before sending (legitimate text should never contain them)
  2. Use the sandbox write_file tool for anything binary — the artifacts editor is text-only by contract
  3. Investigate why the content contains NULs; usually a corrupted source or a misused API

Example fix

// before
await client.put(EDIT_URL, json={"path": p, "content": text_with_nuls, "sha256": sha})

// after
assert !text.includes("\u0000");  // guard client-side
await client.put(EDIT_URL, json={"path": p, "content": text, "sha256": sha})
Defensive patterns

Strategy: validation

Validate before calling

def content_is_nul_free(content: str) -> bool:
    return "\x00" not in content

Type guard

def is_savable_content(content: str) -> bool:
    encoded = content.encode("utf-8")
    return (len(encoded) <= 2 * 1024 * 1024
            and b"\x00" not in encoded)

Prevention

When it happens

Trigger: PUT edit-artifact with a content string containing '\u0000' — programmatic clients injecting serialized/binary data through the JSON content field, or corrupted editor buffers.

Common situations: Scripts treating the edit endpoint as a generic file-write API, copy-paste of terminal control-laden output, or upstream bugs producing NULs in strings.

Related errors


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