{"record":{"id":"c580b35ca91db7ab","repo":"bytedance/deer-flow","slug":"content","errorCode":null,"errorMessage":"content","messagePattern":"content","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py","lineNumber":928,"sourceCode":"        confidence wins, confidence coerced). If the cap evicts the just-added\n        (lower-confidence) fact, ``fact_id`` is ``None`` so callers report\n        \"not stored - cap reached\" instead of a dangling id with a false\n        \"added\" status. This restores both the max_facts cap and the post-trim\n        existence check (upstream's ``create_memory_fact_with_created_fact``),\n        which the vendored copy had dropped together to avoid the dangling id.\n\n        Duplicate rejection is enforced here (not only by callers): the\n        candidate's normalized content key is checked against the fresh\n        memory snapshot inside the revision-conflict retry loop of both\n        storage paths (apply_changes and legacy single-file save), so\n        concurrent creators cannot both store the same content. Raises\n        ``ValueError(\"Duplicate fact\")`` on a normalized-content match.\n        \"\"\"\n        if agent_name is None:\n            raise ValueError(\"agent_name\")\n        normalized_content = content.strip()\n        if not normalized_content:\n            raise ValueError(\"content\")\n        normalized_category = category.strip() or \"context\"\n        validated_confidence = _validate_confidence(confidence)\n        candidate_key = _fact_content_key(normalized_content)\n        now = utc_now_iso_z()\n        fact_id = f\"fact_{uuid.uuid4().hex[:8]}\"\n        candidate = {\n            \"id\": fact_id,\n            \"content\": normalized_content,\n            \"category\": normalized_category,\n            \"confidence\": validated_confidence,\n            \"createdAt\": now,\n            \"source\": \"manual\",\n        }\n        if getattr(type(self._storage), \"apply_changes\", None) is not MemoryStorage.apply_changes:\n            for attempt in range(3):\n                memory_data = self.get_memory_data(agent_name, user_id=user_id) if attempt == 0 else self.reload_memory_data(agent_name, user_id=user_id)\n                # Duplicate rejection lives inside the conflict-retry loop so\n                # it is re-evaluated against the fresh snapshot after every","sourceCodeStart":910,"sourceCodeEnd":946,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py#L910-L946","documentation":"create_memory_fact strips the content argument and raises ValueError('content') when the result is empty. Whitespace-only strings are treated as absent content, because a fact with no text is unstoreable and would also collide on the normalized-content duplicate key.","triggerScenarios":"Calling create_memory_fact('') or create_memory_fact('   \\n  '), or forwarding user/LLM input that was never checked for blankness (empty form field, model returned empty string).","commonSituations":"Memory-add tool wired directly to a chat input with no required-field validation, or an extraction prompt whose model output collapses to whitespace.","solutions":["Validate and reject blank input before calling: if not content or not content.strip(): return error","Trim input at the API boundary so downstream code sees normalized text","If the value comes from an LLM, add a retry/check for empty extraction results"],"exampleFix":"// before\nfact_id = memory.create_memory_fact(content=user_text, agent_name=agent).1\n// after\nif not user_text or not user_text.strip():\n    raise HTTPException(400, \"content must not be empty\")\n_, fact_id = memory.create_memory_fact(content=user_text.strip(), agent_name=agent)","handlingStrategy":"validation","validationCode":"normalized = (content or \"\").strip()\nif not normalized:\n    raise HTTPException(400, \"content must not be empty\")","typeGuard":"def is_nonblank(value: str | None) -> TypeGuard[str]:\n    return isinstance(value, str) and bool(value.strip())","tryCatchPattern":"try:\n    memory.create_memory_fact(content, agent_name=agent)\nexcept ValueError as e:\n    if str(e) == \"content\":\n        raise HTTPException(400, \"content must not be empty\")\n    raise","preventionTips":["Trim and check user/model input at the API boundary","Treat whitespace-only extraction results as 'nothing to store', not as facts"],"tags":["memory","deermem","validation","empty-input"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}