{"record":{"id":"9bdca864fcb57ed5","repo":"bytedance/deer-flow","slug":"stored-fact-normalized-id-r-has-an-invalid-re","errorCode":null,"errorMessage":"Stored fact {normalized['id']!r} has an invalid revision","messagePattern":"Stored fact (.+?) has an invalid revision","errorType":"exception","errorClass":"MemoryStorageCorruption","httpStatus":null,"severity":"critical","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py","lineNumber":236,"sourceCode":"            normalized[\"source\"] = {\"type\": \"conversation\", \"threadId\": source}\n    elif not isinstance(source, dict):\n        normalized[\"source\"] = {\"type\": \"unknown\", \"threadId\": None}\n    else:\n        normalized[\"source\"].setdefault(\"type\", \"unknown\")\n        if not isinstance(normalized[\"source\"].get(\"type\"), str):\n            raise ValueError(\"fact.source.type must be a string\")\n        if normalized[\"source\"].get(\"threadId\") is not None and not isinstance(normalized[\"source\"].get(\"threadId\"), str):\n            raise ValueError(\"fact.source.threadId must be a string or null\")\n    normalized[\"title\"] = _fact_title(normalized)\n    now = utc_now_iso_z()\n    if existing is None:\n        normalized.setdefault(\"createdAt\", now)\n        normalized.setdefault(\"updatedAt\", normalized[\"createdAt\"])\n        normalized[\"revision\"] = revision\n    else:\n        existing_revision = existing.get(\"revision\")\n        if not isinstance(existing_revision, int) or existing_revision < 1:\n            raise MemoryStorageCorruption(f\"Stored fact {normalized['id']!r} has an invalid revision\")\n        if revision != existing_revision:\n            raise MemoryFactRevisionConflict(f\"Expected fact {normalized['id']!r} revision {revision}, found {existing_revision}\")\n        normalized[\"createdAt\"] = existing.get(\"createdAt\") or normalized.get(\"createdAt\") or now\n        comparison_keys = {\"revision\", \"updatedAt\"}\n        incoming_material = {key: value for key, value in normalized.items() if key not in comparison_keys}\n        existing_material = {key: value for key, value in existing.items() if key not in comparison_keys}\n        if incoming_material == existing_material:\n            normalized[\"revision\"] = existing_revision\n            normalized[\"updatedAt\"] = existing.get(\"updatedAt\") or normalized[\"createdAt\"]\n        else:\n            normalized[\"revision\"] = existing_revision + 1\n            normalized[\"updatedAt\"] = now\n    if not isinstance(normalized.get(\"createdAt\"), str) or not isinstance(normalized.get(\"updatedAt\"), str):\n        raise ValueError(\"fact.createdAt and fact.updatedAt must be strings\")\n    if normalized[\"consolidatedFrom\"]:\n        normalized.setdefault(\"consolidatedAt\", normalized[\"updatedAt\"])\n    return normalized\n","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py#L218-L254","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-edited memory JSON; older versions writing a different revision shape; partial writes from crashes; external sync tools merging JSON badly.","solutions":["Locate the fact file named by the id in the message and inspect its revision field.","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.","Audit the whole memory directory for other malformed revisions and re-run the store's consistency/repair tooling if available."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"rev = existing_record.get(\"revision\")\nif isinstance(rev, bool) or not isinstance(rev, int) or rev < 1:\n    quarantine(existing_record)  # do not attempt rebase on damaged data","typeGuard":"def stored_fact_revision_ok(record: dict) -> bool:\n    r = record.get(\"revision\")\n    return not isinstance(r, bool) and isinstance(r, int) and r >= 1","tryCatchPattern":"try:\n    store.save(fact)\nexcept MemoryStorageCorruption as exc:\n    if \"invalid revision\" in str(exc):\n        # repair: back up, set stored revision to 1 (or delete record), retry once\n        raise","preventionTips":["Never hand-edit revision fields in stored JSON.","Keep memory directories out of sync-tool conflict merges.","Back up before bulk edits so damaged records can be restored."],"tags":["deermem","memory","data-corruption","optimistic-concurrency"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}