bytedance/deer-flow · critical · MemoryStorageCorruption

Legacy {section} summary migration conflict in {legacy_path}

Error message

Legacy {section} summary migration conflict in {legacy_path}; the legacy file was kept

What it means

Thrown while migrating a legacy summary file into canonical storage: both the canonical section and the legacy section contain meaningful (non-empty) data and they differ, so the migrator cannot tell which copy is authoritative. Adopting the legacy section would silently overwrite live data, so it refuses and keeps the legacy file on disk. This is a MemoryStorageCorruption signal requiring operator judgment, not a bug to paper over.

Source

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

    }


def _has_meaningful_data(value: Any) -> bool:
    """Return whether a legacy summary value contains anything worth preserving."""
    if isinstance(value, dict):
        return any(_has_meaningful_data(item) for item in value.values())
    if isinstance(value, (list, tuple, set)):
        return any(_has_meaningful_data(item) for item in value)
    return value not in (None, "", False)


def _merge_legacy_summary_section(*, canonical: Any, legacy: Any, section: str, legacy_path: Path) -> Any:
    """Adopt a legacy section only when doing so cannot overwrite live data."""
    if canonical == legacy or not _has_meaningful_data(legacy):
        return copy.deepcopy(canonical)
    if not _has_meaningful_data(canonical):
        return copy.deepcopy(legacy)
    raise MemoryStorageCorruption(f"Legacy {section} summary migration conflict in {legacy_path}; the legacy file was kept")


def _scope_dict(user_id: str | None, agent_name: str | None) -> dict[str, str | None]:
    return {"userId": user_id, "agentName": agent_name}


def _content_hash(raw: bytes) -> str:
    return f"sha256:{hashlib.sha256(raw).hexdigest()}"


def _file_signature(path: Path) -> tuple[int, int] | None:
    """Use nanosecond mtime plus size so cache validation is not mtime-only."""
    try:
        stat = path.stat()
        return (stat.st_mtime_ns, stat.st_size)
    except OSError:
        return None

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Inspect both files named in the message: keep the section whose content is correct/current, and manually delete or empty the other, then retry the operation that triggered migration.
  2. If the legacy file is the good copy, remove the conflicting canonical section so it has no meaningful data and the legacy section is adopted cleanly.
  3. As a last resort, back up the whole memory directory and re-initialize the store, restoring facts manually.
Defensive patterns

Strategy: try-catch

Validate before calling

# before triggering migration, detect the conflict yourself
def legacy_conflicts(canonical: dict, legacy: dict) -> bool:
    return any(
        canonical.get(k) != legacy.get(k) and canonical.get(k) and legacy.get(k)
        for k in set(canonical) | set(legacy)
    )

Try / catch

try:
    store.upgrade_legacy()
except MemoryStorageCorruption as exc:
    if "migration conflict" in str(exc):
        # both file paths are in the message; halt and page a human
        # do NOT delete either file automatically
        raise

Prevention

When it happens

Trigger: A v1 legacy summary file exists beside a canonical store that was already written by v2 code (e.g. the store was partially migrated, then a new session wrote fresh summary data), and the two sections diverge. Also occurs when users hand-edit either JSON file or restore one file from an older backup.

Common situations: Downgrade/upgrade cycles between DeerFlow versions; restoring canonical files from a backup while keeping the newer legacy file; two machines or containers writing to the same memory directory.

Related errors


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