{"repo":"NousResearch/hermes-agent","public_id":"28291","display_label":"Moonshot schema crashes on JSON Schema union types","summary":"Multiple bug reports and fix attempts addressing a TypeError caused by unhashable list values when Moonshot/Kimi tool schema sanitization encounters JSON Schema union type arrays. The fixes target two crash sites: _fill_missing_type and sanitize_moonshot_tools.","subsystem":"agent","counts":{"members":12,"open_issues":0,"open_issues_distinct":0,"open_prs":2,"closed_issues":3,"merged_prs":1,"attempt_prs":8},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":45579,"merged_at":"2026-06-13T12:51:42+00:00"},"resolution":{"state":"partially_resolved","by":"pr-45579"},"freshness":{"state":"verified","last_verified_at":"2026-06-13T12:51:42+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.16787878787878788},"fix_edges":[{"issue_number":30095,"pr_number":45579,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-13T12:51:42+00:00"}],"members":[{"kind":"issue","number":28291,"title":"Bug: moonshot_schema._fill_missing_type crashes on JSON Schema union types (list type)","url":null,"state":"closed"},{"kind":"issue","number":28787,"title":"TypeError: unhashable type: 'list' on kimi-k2.6:cloud via Ollama AND direct cloud — response parsing bug","url":null,"state":"closed"},{"kind":"issue","number":30095,"title":"kimi-k2.6: TypeError: unhashable type 'list' in sanitize_moonshot_tools when tool has union type parameter","url":null,"state":"closed"},{"kind":"pr","number":28322,"title":"fix: handle Moonshot union type lists","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7ced2c629f5c36387f01c4ceb5fb4c9b","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":28422,"title":"fix: handle JSON Schema union type lists in moonshot_schema (both crash sites)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7ced2c629f5c36387f01c4ceb5fb4c9b","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28772,"title":"fix(agent): accept union types in Moonshot schema sanitizer","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7ced2c629f5c36387f01c4ceb5fb4c9b","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28879,"title":"fix(moonshot): flatten union types before set check to avoid TypeError (#28787, #28291)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28422","number":28422},{"kind":"pr","value":"#28772","number":28772},{"kind":"pr","value":"#28322","number":28322}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":29228,"title":"fix(moonshot): handle JSON Schema union types in _fill_missing_type (#28291)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28422","number":28422}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":30226,"title":"fix(moonshot): handle union type arrays in sanitize_moonshot_tools (#30095)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7","number":0},{"kind":"pr","value":"#45579","number":45579}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":30753,"title":"fix(moonshot): normalise union type arrays in _fill_missing_type to prevent TypeError","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7ced2c629f5c36387f01c4ceb5fb4c9b","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":32516,"title":"fix(parser): handle list-form response field for kimi-k2 cloud (#28787)","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"5acd185f7ced","number":0},{"kind":"pr","value":"#28787","number":28787}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":45579,"title":"fix(moonshot): handle union type arrays in tool schemas","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/moonshot_schema.py","tests/agent/test_moonshot_schema.py"],"fix_hunks":[{"file":"agent/moonshot_schema.py","hunks":["@@ -135,7 +135,14 @@ def _repair_schema(node: Any, is_schema: bool = True) -> Any:\n \n def _fill_missing_type(node: Dict[str, Any]) -> Dict[str, Any]:\n     \"\"\"Infer a reasonable ``type`` if this schema node has none.\"\"\"\n-    if \"type\" in node and node[\"type\"] not in {None, \"\"}:\n+    node_type = node.get(\"type\")\n+    if isinstance(node_type, list):\n+        concrete = next(\n+            (t for t in node_type if isinstance(t, str) and t not in {\"\", \"null\"}),\n+            \"string\",\n+        )\n+        return {**node, \"type\": concrete}\n+    if \"type\" in node and node_type not in {None, \"\"}:\n         return node\n \n     # Heuristic: presence of ``properties`` → object, ``items`` → array, ``enum``"]},{"file":"tests/agent/test_moonshot_schema.py","hunks":["@@ -397,3 +397,52 @@ def test_anyof_collapse_still_runs_nullable_and_enum_cleanup(self):\n         assert db_type[\"type\"] == \"string\"\n         assert db_type[\"enum\"] == [\"mysql\", \"postgresql\"], \\\n             \"null/empty enum values must be stripped after anyOf collapse\"\n+\n+\n+class TestUnionTypeList:\n+    \"\"\"Moonshot sanitizer accepts JSON Schema union type arrays.\"\"\"\n+\n+    def test_union_type_list_normalizes_to_first_concrete_type(self):\n+        params = {\n+            \"type\": \"object\",\n+            \"properties\": {\n+                \"limit\": {\n+                    \"type\": [\"number\", \"string\"],\n+                    \"description\": \"Max results\",\n+                },\n+            },\n+        }\n+\n+        out = sanitize_moonshot_tool_parameters(params)\n+\n+        assert out[\"properties\"][\"limit\"][\"type\"] == \"number\"\n+\n+    def test_union_type_list_skips_null_type(self):\n+        params = {\n+            \"type\": \"object\",\n+            \"properties\": {\n+                \"name\": {\"type\": [\"null\", \"string\"]},\n+            },\n+        }\n+\n+        out = sanitize_moonshot_tool_parameters(params)\n+\n+        assert out[\"properties\"][\"name\"][\"type\"] == \"string\"\n+\n+    def test_union_type_list_with_enum_does_not_crash_or_mutate_input(self):\n+        params = {\n+            \"type\": \"object\",\n+            \"properties\": {\n+                \"sort\": {\n+                    \"type\": [\"string\", \"null\"],\n+                    \"enum\": [\"asc\", \"desc\", None, \"\"],\n+                },\n+            },\n+        }\n+\n+        out = sanitize_moonshot_tool_parameters(params)\n+\n+        sort = out[\"properties\"][\"sort\"]\n+        assert sort[\"type\"] == \"string\"\n+        assert sort[\"enum\"] == [\"asc\", \"desc\"]\n+        assert params[\"properties\"][\"sort\"][\"type\"] == [\"string\", \"null\"]\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}