{"record":{"id":"48f254a93a3a903a","repo":"MemPalace/mempalace","slug":"invalid-repair-backup-record-at-line-line-number","errorCode":null,"errorMessage":"invalid repair backup record at line {line_number}","messagePattern":"invalid repair backup record at line (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mempalace/encoding_repair.py","lineNumber":430,"sourceCode":"            if not line.strip():\n                continue\n\n            try:\n                record = json.loads(line)\n            except json.JSONDecodeError as exc:\n                raise ValueError(f\"invalid backup JSON at line {line_number}\") from exc\n\n            drawer_id = record.get(\"id\") if isinstance(record, dict) else None\n            document = record.get(\"original_document\") if isinstance(record, dict) else None\n\n            if not isinstance(\n                drawer_id,\n                str,\n            ) or not isinstance(\n                document,\n                str,\n            ):\n                raise ValueError(f\"invalid repair backup record at line {line_number}\")\n\n            yield drawer_id, document\n\n\ndef repair_collection(\n    collection,\n    *,\n    apply: bool = False,\n    page_size: int = 500,\n    backup_path: Optional[Union[str, Path]] = None,\n    on_change: Optional[Callable[[str, str, str], None]] = None,\n) -> dict:\n    \"\"\"Scan a collection and optionally repair high-confidence mojibake.\"\"\"\n    if page_size < 1:\n        raise ValueError(\"page_size must be at least 1\")\n\n    if apply and backup_path is None:\n        raise ValueError(\"backup_path is required when apply=True\")","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/MemPalace/mempalace/blob/06cb6987f02610784fefbad4b2bd5d026d164ba6/mempalace/encoding_repair.py#L412-L448","documentation":"Raised by _iter_backup_records when a record line is valid JSON but is not a dict-bearing the required shape: the 'id' field must be a str and the 'original_document' field must be a str. Non-dict records (lists, strings, numbers) or records missing either field, or with non-string values (null, number), are rejected with the line number. This guarantees restore only replays well-formed drawer-id/document pairs.","triggerScenarios":"A backup record whose 'original_document' is null (drawer had empty content and the writer serialized null), an 'id' that is numeric, a record that is a bare list, or a schema drift between mempalace versions writing different field names.","commonSituations":"Backups from older mempalace versions with a different record schema; third-party scripts generating pseudo-backups; JSON middleware converting null handling; manual record additions.","solutions":["Inspect the named line and check both fields: must be JSON objects with string 'id' and string 'original_document'","If the backup came from another mempalace version, restore under that version or write a small migration that rewrites records to the current schema","If 'original_document' is null because content was empty, rewrite it as \"\" — the type gate requires str","Regenerate the backup via repair_collection on the current version rather than patching old files"],"exampleFix":"# migrate an old-schema backup line\n# before: {\"id\": 42, \"original_document\": null}\n# after:  {\"id\": \"42\", \"original_document\": \"\"}\nimport json\nlines = open('backup.jsonl', encoding='utf-8').read().splitlines()\nheader, records = lines[0], lines[1:]\nfixed = [json.dumps({\"id\": str(r[\"id\"]), \"original_document\": r.get(\"original_document\") or \"\"})\n         for r in map(json.loads, records) if isinstance(r, dict)]\nopen('backup-fixed.jsonl', 'w', encoding='utf-8').write('\\n'.join([header] + fixed) + '\\n')","handlingStrategy":"type-guard","validationCode":"def backup_records_well_typed(path) -> bool:\n    with open(path, encoding='utf-8') as fh:\n        fh.readline()\n        for line in fh:\n            if not line.strip():\n                continue\n            rec = json.loads(line)\n            if not (isinstance(rec, dict) and isinstance(rec.get('id'), str)\n                    and isinstance(rec.get('original_document'), str)):\n                return False\n    return True","typeGuard":"def is_valid_backup_record(rec) -> bool:\n    return (isinstance(rec, dict)\n            and isinstance(rec.get('id'), str)\n            and isinstance(rec.get('original_document'), str))","tryCatchPattern":"try:\n    restore_backup(collection, backup_path=path)\nexcept ValueError as e:\n    if \"invalid repair backup record at line\" in str(e):\n        sys.exit(f\"Record schema mismatch at the named line — migrate the backup or regenerate it\")","preventionTips":["Generate backups only with the same (or tested-compatible) mempalace version that will restore them","If migrating old backups, validate every record with the type guard above first","Never inject records into a backup by hand"],"tags":["backup","json","schema","repair","validation"],"backgroundTag":null,"analyzedSha":"06cb6987f02610784fefbad4b2bd5d026d164ba6","analyzedAt":"2026-08-15T03:03:36.213Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}