bytedance/deer-flow · critical · MemoryStorageCorruption

Stored fact {normalized['id']!r} has an invalid revision

Error message

Stored fact {normalized['id']!r} has an invalid revision

What it means

While rebasing an update onto an existing stored fact, the stored fact's own 'revision' field is not a valid int >= 1, so the optimistic-concurrency comparison cannot proceed. Raised as MemoryStorageCorruption: the on-disk store itself is damaged, independent of the incoming payload.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:236

            normalized["source"] = {"type": "conversation", "threadId": source}
    elif not isinstance(source, dict):
        normalized["source"] = {"type": "unknown", "threadId": None}
    else:
        normalized["source"].setdefault("type", "unknown")
        if not isinstance(normalized["source"].get("type"), str):
            raise ValueError("fact.source.type must be a string")
        if normalized["source"].get("threadId") is not None and not isinstance(normalized["source"].get("threadId"), str):
            raise ValueError("fact.source.threadId must be a string or null")
    normalized["title"] = _fact_title(normalized)
    now = utc_now_iso_z()
    if existing is None:
        normalized.setdefault("createdAt", now)
        normalized.setdefault("updatedAt", normalized["createdAt"])
        normalized["revision"] = revision
    else:
        existing_revision = existing.get("revision")
        if not isinstance(existing_revision, int) or existing_revision < 1:
            raise MemoryStorageCorruption(f"Stored fact {normalized['id']!r} has an invalid revision")
        if revision != existing_revision:
            raise MemoryFactRevisionConflict(f"Expected fact {normalized['id']!r} revision {revision}, found {existing_revision}")
        normalized["createdAt"] = existing.get("createdAt") or normalized.get("createdAt") or now
        comparison_keys = {"revision", "updatedAt"}
        incoming_material = {key: value for key, value in normalized.items() if key not in comparison_keys}
        existing_material = {key: value for key, value in existing.items() if key not in comparison_keys}
        if incoming_material == existing_material:
            normalized["revision"] = existing_revision
            normalized["updatedAt"] = existing.get("updatedAt") or normalized["createdAt"]
        else:
            normalized["revision"] = existing_revision + 1
            normalized["updatedAt"] = now
    if not isinstance(normalized.get("createdAt"), str) or not isinstance(normalized.get("updatedAt"), str):
        raise ValueError("fact.createdAt and fact.updatedAt must be strings")
    if normalized["consolidatedFrom"]:
        normalized.setdefault("consolidatedAt", normalized["updatedAt"])
    return normalized

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Locate the fact file named by the id in the message and inspect its revision field.
  2. Repair the stored fact by setting revision to a valid positive int (1 if unsure) after backing up the file, or delete the corrupted record so it can be recreated.
  3. Audit the whole memory directory for other malformed revisions and re-run the store's consistency/repair tooling if available.
Defensive patterns

Strategy: try-catch

Validate before calling

rev = existing_record.get("revision")
if isinstance(rev, bool) or not isinstance(rev, int) or rev < 1:
    quarantine(existing_record)  # do not attempt rebase on damaged data

Type guard

def stored_fact_revision_ok(record: dict) -> bool:
    r = record.get("revision")
    return not isinstance(r, bool) and isinstance(r, int) and r >= 1

Try / catch

try:
    store.save(fact)
except MemoryStorageCorruption as exc:
    if "invalid revision" in str(exc):
        # repair: back up, set stored revision to 1 (or delete record), retry once
        raise

Prevention

When it happens

Trigger: An update to a fact whose stored JSON has revision='2', revision=0, or a bad value set by a prior hand-edit or a partially failed migration. Distinguishing feature vs error 496: here the STORED value is malformed, not merely different.

Common situations: Hand-edited memory JSON; older versions writing a different revision shape; partial writes from crashes; external sync tools merging JSON badly.

Related errors


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