fix(session_search): coerce limit to int to prevent TypeError with non-int values (#10522)

Models (especially open-source like qwen3.5-plus) may send non-int values
for the limit parameter — None (JSON null), string, or even a type object.
This caused TypeError: '<=' not supported between instances of 'int' and
'type' when the value reached min()/comparison operations.

Changes:
- Add defensive int coercion at session_search() entry with fallback to 3
- Clamp limit to [1, 5] range (was only capped at 5, not floored)
- Add tests for None, type object, string, negative, and zero limit values

Reported by community user ludoSifu via Discord.
This commit is contained in:
Teknium
2026-04-15 14:11:05 -07:00
committed by GitHub
parent 91980e3518
commit 824c33729d
2 changed files with 66 additions and 1 deletions

View File

@@ -290,6 +290,63 @@ class TestSessionSearch:
assert result["results"] == [] assert result["results"] == []
assert result["sessions_searched"] == 0 assert result["sessions_searched"] == 0
def test_limit_none_coerced_to_default(self):
"""Model sends limit=null → should fall back to 3, not TypeError."""
from unittest.mock import MagicMock
from tools.session_search_tool import session_search
mock_db = MagicMock()
mock_db.search_messages.return_value = []
result = json.loads(session_search(
query="test", db=mock_db, limit=None,
))
assert result["success"] is True
def test_limit_type_object_coerced_to_default(self):
"""Model sends limit as a type object → should fall back to 3, not TypeError."""
from unittest.mock import MagicMock
from tools.session_search_tool import session_search
mock_db = MagicMock()
mock_db.search_messages.return_value = []
result = json.loads(session_search(
query="test", db=mock_db, limit=int,
))
assert result["success"] is True
def test_limit_string_coerced(self):
"""Model sends limit as string '2' → should coerce to int."""
from unittest.mock import MagicMock
from tools.session_search_tool import session_search
mock_db = MagicMock()
mock_db.search_messages.return_value = []
result = json.loads(session_search(
query="test", db=mock_db, limit="2",
))
assert result["success"] is True
def test_limit_clamped_to_range(self):
"""Negative or zero limit should be clamped to 1."""
from unittest.mock import MagicMock
from tools.session_search_tool import session_search
mock_db = MagicMock()
mock_db.search_messages.return_value = []
result = json.loads(session_search(
query="test", db=mock_db, limit=-5,
))
assert result["success"] is True
result = json.loads(session_search(
query="test", db=mock_db, limit=0,
))
assert result["success"] is True
def test_current_root_session_excludes_child_lineage(self): def test_current_root_session_excludes_child_lineage(self):
"""Delegation child hits should be excluded when they resolve to the current root session.""" """Delegation child hits should be excluded when they resolve to the current root session."""
from unittest.mock import MagicMock from unittest.mock import MagicMock

View File

@@ -310,7 +310,15 @@ def session_search(
if db is None: if db is None:
return tool_error("Session database not available.", success=False) return tool_error("Session database not available.", success=False)
limit = min(limit, 5) # Cap at 5 sessions to avoid excessive LLM calls # Defensive: models (especially open-source) may send non-int limit values
# (None when JSON null, string "int", or even a type object). Coerce to a
# safe integer before any arithmetic/comparison to prevent TypeError.
if not isinstance(limit, int):
try:
limit = int(limit)
except (TypeError, ValueError):
limit = 3
limit = max(1, min(limit, 5)) # Clamp to [1, 5]
# Recent sessions mode: when query is empty, return metadata for recent sessions. # Recent sessions mode: when query is empty, return metadata for recent sessions.
# No LLM calls — just DB queries for titles, previews, timestamps. # No LLM calls — just DB queries for titles, previews, timestamps.