{"record":{"id":"04eb2e7dede4f88c","repo":"bytedance/deer-flow","slug":"guaranteed-categories-must-be-an-iterable-of-strin","errorCode":null,"errorMessage":"guaranteed_categories must be an iterable of strings, not a bare str","messagePattern":"guaranteed_categories must be an iterable of strings, not a bare str","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/prompt.py","lineNumber":507,"sourceCode":"            the budget becomes truly additive only when the guaranteed lines\n            alone would push the assembled output past *max_tokens*, at which\n            point the safety-truncation ceiling is raised to\n            ``max_tokens + guaranteed_actual_usage`` to protect them.\n            Ignored when *guaranteed_categories* is ``None`` or empty.\n\n    Returns:\n        Formatted memory string for system prompt injection.\n    \"\"\"\n    if not memory_data:\n        return \"\"\n\n    # Reject a bare string explicitly: iterating a ``str`` yields single\n    # characters, which would silently produce a meaningless frozenset of\n    # letters and turn the guarantee off without any warning.  Config-layer\n    # callers go through Pydantic (which enforces ``list[str]``), so this\n    # only guards the public helper surface.\n    if isinstance(guaranteed_categories, str):\n        raise TypeError(\"guaranteed_categories must be an iterable of strings, not a bare str\")\n    effective_guaranteed: frozenset[str] = frozenset(c.strip() for c in guaranteed_categories if isinstance(c, str) and c.strip()) if guaranteed_categories else frozenset()\n\n    sections: list[str] = []\n\n    # Format user context\n    user_data = memory_data.get(\"user\", {})\n    if user_data:\n        user_sections = []\n\n        work_ctx = user_data.get(\"workContext\", {})\n        if work_ctx.get(\"summary\"):\n            user_sections.append(f\"Work: {_escape_summary(work_ctx['summary'])}\")\n\n        personal_ctx = user_data.get(\"personalContext\", {})\n        if personal_ctx.get(\"summary\"):\n            user_sections.append(f\"Personal: {_escape_summary(personal_ctx['summary'])}\")\n\n        top_of_mind = user_data.get(\"topOfMind\", {})","sourceCodeStart":489,"sourceCodeEnd":525,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/prompt.py#L489-L525","documentation":"TypeError from format_memory_prompt (deermem): guaranteed_categories was passed as a bare str. Iterating a string yields characters, which would silently build a meaningless frozenset of letters and disable the guarantee with no warning, so the helper rejects strings explicitly. Config-layer callers are protected by Pydantic list[str]; this guard covers direct calls to the public helper.","triggerScenarios":"Calling format_memory_prompt(memory_data, guaranteed_categories=\"preferences\") instead of [\"preferences\"]. Any single-string argument that should be an iterable of category names triggers it.","commonSituations":"Quick script or test passing a single category as a string; refactor that replaced a list variable with a scalar; dataclass field typed str instead of list[str] feeding the helper.","solutions":["Pass a list/tuple of category strings: guaranteed_categories=[\"preferences\"].","If the variable may be either, normalize: cats = [cats] if isinstance(cats, str) else cats.","Type the producing field as list[str] in Pydantic/dataclass so the mistake is caught at validation time."],"exampleFix":"# before\nformat_memory_prompt(data, guaranteed_categories=\"identity\")\n\n# after\nformat_memory_prompt(data, guaranteed_categories=[\"identity\"])","handlingStrategy":"type-guard","validationCode":"cats = guaranteed_categories\nif isinstance(cats, str):\n    cats = [cats]  # or raise early with your own clearer message\nformatted = format_memory_prompt(memory_data, guaranteed_categories=cats)","typeGuard":"def is_str_iterable_not_str(v) -> bool:\n    return not isinstance(v, str) and isinstance(v, (list, tuple, set, frozenset)) and all(isinstance(x, str) for x in v)","tryCatchPattern":null,"preventionTips":["Type the field list[str] in Pydantic models so validation catches it at config load.","Never pass a single category as a scalar; always wrap in a list.","Add a unit test for both str and list inputs on helpers that take category collections."],"tags":["typing","deermem","memory","prompt","typeerror"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}