{"record":{"id":"b3d4396eb6d2dc7d","repo":"HKUDS/DeepTutor","slug":"add-refs-must-be-non-empty","errorCode":null,"errorMessage":"add: refs must be non-empty","messagePattern":"add: refs must be non-empty","errorType":"validation","errorClass":"OpValidationError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/memory/ops.py","lineNumber":81,"sourceCode":"\nclass OpValidationError(Exception):\n    \"\"\"Raised when a batch fails pre-flight validation.\"\"\"\n\n\ndef _validate(doc: Document, ops: list[Op]) -> None:\n    edits: set[str] = set()\n    deletes: set[str] = set()\n\n    for op in ops:\n        if isinstance(op, AddOp):\n            if not op.text or len(op.text) > _MAX_TEXT_LEN:\n                raise OpValidationError(\n                    f\"add: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.text)})\"\n                )\n            if not op.section or len(op.section) > _MAX_SECTION_LEN:\n                raise OpValidationError(f\"add: invalid section {op.section!r}\")\n            if not op.refs:\n                raise OpValidationError(\"add: refs must be non-empty\")\n            for ref in op.refs:\n                if not is_valid_ref(ref):\n                    raise OpValidationError(f\"add: malformed ref {ref!r}\")\n        elif isinstance(op, EditOp):\n            if not is_entry_id(op.target_id):\n                raise OpValidationError(f\"edit: malformed target_id {op.target_id!r}\")\n            if doc.find(op.target_id) is None:\n                raise OpValidationError(f\"edit: target_id {op.target_id} not found\")\n            if not op.new_text or len(op.new_text) > _MAX_TEXT_LEN:\n                raise OpValidationError(\n                    f\"edit: text length must be 1..{_MAX_TEXT_LEN} (got {len(op.new_text)})\"\n                )\n            if not op.new_refs:\n                raise OpValidationError(\"edit: refs must be non-empty\")\n            for ref in op.new_refs:\n                if not is_valid_ref(ref):\n                    raise OpValidationError(f\"edit: malformed ref {ref!r}\")\n            if op.target_id in deletes:","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/memory/ops.py#L63-L99","documentation":"OpValidationError raised in _validate when an AddOp carries an empty refs list. Every memory entry must cite at least one provenance reference (source message/span), which is what later consolidation and undo rely on; an entry with no refs cannot be traced back and is rejected before apply mutates anything.","triggerScenarios":"AddOp(text=..., section=..., refs=[]) or refs=None passed to ops.apply — typically a caller forgetting to thread the originating message/source span into the op, or an LLM tool-call omitting the refs argument.","commonSituations":"New integration code writing memories without capturing source references; tests building AddOp fixtures minimally; ref extraction step returning an empty list that is passed through unchecked.","solutions":["Always derive refs from the triggering source (e.g. message id / span ref) and pass them in AddOp.refs","If your pipeline has no real source, synthesize a stable ref (e.g. 'manual:<uuid>' if the ref grammar allows) rather than an empty list","Guard upstream: skip the write and log when the ref-extraction step yields nothing"],"exampleFix":"# before\nop = AddOp(text=note, section=\"facts\", refs=[])\n\n# after\nif not refs:\n    refs = [f\"msg:{source_message_id}\"]\nop = AddOp(text=note, section=\"facts\", refs=refs)","handlingStrategy":"validation","validationCode":"refs = [r for r in raw_refs if r]\nif not refs:\n    refs = [f\"msg:{source_message_id}\"]","typeGuard":"def has_valid_refs(refs) -> bool:\n    return isinstance(refs, (list, tuple)) and len(refs) > 0","tryCatchPattern":"try:\n    ops.apply(doc, [op])\nexcept OpValidationError as e:\n    if \"refs must be non-empty\" in str(e):\n        op = replace(op, refs=[fallback_ref])\n        ops.apply(doc, [op])\n    else:\n        raise","preventionTips":["Thread source provenance into every memory write from the start","Skip+log writes whose ref extraction came back empty","Require refs in the tool schema so LLM calls can't omit them"],"tags":["validation","memory","ops","provenance"],"backgroundTag":"payload-validation-failed","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}