bytedance/deer-flow · error · HTTPException

Artifact changed since it was opened

Error message

Artifact changed since it was opened

What it means

HTTP 412 from _load_editable_artifact: sha256 of the current file bytes does not match the expected_sha256 the client sent with the edit. This is optimistic-concurrency control: the artifact changed on disk since the client opened it, and saving would silently clobber someone else's (or another run's) writes.

Source

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

        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")
    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

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Re-open the artifact to get fresh content and the new sha256, merge your changes, then save again
  2. Avoid concurrent editors on the same thread's artifacts — coordinate through one session
  3. Clients should always send the sha256 obtained from the most recent GET, not a cached one

Example fix

// before: sha256 cached from an old listing → 412
payload = {"path": p, "content": new_text, "sha256": cached_sha}

// after: fetch fresh content+sha right before saving
fresh = await client.get(artifact_url(p))
payload = {"path": p, "content": merge(fresh.text, local_edits), "sha256": sha256(fresh_bytes)}
await client.put(EDIT_URL, json=payload)
Defensive patterns

Strategy: validation

Validate before calling

import hashlib

def sha_matches(actual_path: str, expected_sha256: str) -> bool:
    h = hashlib.sha256()
    with open(actual_path, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 16), b""):
            h.update(chunk)
    return h.hexdigest() == expected_sha256

Try / catch

for attempt in range(3):
    resp = await client.put(EDIT_URL, json=payload)
    if resp.status_code != 412:
        break
    fresh = await client.get(artifact_url(path))           # reload
    payload["content"] = merge(fresh.text, payload["content"])
    payload["sha256"] = sha256_of(fresh)                     # then retry
else:
    raise ConflictError("artifact kept changing")

Prevention

When it happens

Trigger: PUT edit-artifact where two sessions/tabs edit the same file, the agent rewrites the artifact between open and save, or a run re-executed and regenerated outputs while an editor was open.

Common situations: Multi-tab editing, long-held editor sessions across run retries, mobile+desktop editing the same artifact, or automated clients caching stale sha256 from an old listing.

Related errors


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