From e0e65c44f64b0a1acf989125b950cb6911aa9bce Mon Sep 17 00:00:00 2001 From: Chris Bartholomew Date: Tue, 7 Apr 2026 03:26:27 -0400 Subject: [PATCH] fix(query_analyzer): handle dateparser internal crashes gracefully (#893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DateparserQueryAnalyzer.analyze() called dateparser.search.search_dates() without any error handling, so internal bugs in the third-party library propagated all the way up the search/consolidation pipeline and failed the calling task. Observed traceback: File ".../engine/query_analyzer.py", line 140, in analyze results = self._search_dates(query, settings=settings) File ".../dateparser/search/search.py", line 294, in search_dates "Dates": self.search.search_parse(...) File ".../dateparser/search/search.py", line 168, in search_parse translated, original = self.search(shortname, text, settings) File ".../dateparser/languages/locale.py", line 224, in translate_search [original_tokens[i], original_tokens[i + 1]], IndexError: list index out of range Wrap the call in a try/except so any parser failure is treated as "no temporal constraint found" — the caller can then fall back to non-temporal retrieval instead of erroring out the whole task. The failure is logged at WARNING level so we still notice it. Add a regression test that monkey-patches _search_dates to raise an IndexError and asserts the analyzer returns an empty constraint and emits a warning log. --- .../hindsight_api/engine/query_analyzer.py | 16 ++++++++- .../tests/test_query_analyzer.py | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/hindsight-api-slim/hindsight_api/engine/query_analyzer.py b/hindsight-api-slim/hindsight_api/engine/query_analyzer.py index a78581c7..607e3d57 100644 --- a/hindsight-api-slim/hindsight_api/engine/query_analyzer.py +++ b/hindsight-api-slim/hindsight_api/engine/query_analyzer.py @@ -137,7 +137,21 @@ class DateparserQueryAnalyzer(QueryAnalyzer): "RETURN_AS_TIMEZONE_AWARE": False, } - results = self._search_dates(query, settings=settings) + # Wrap dateparser in a defensive try/except. dateparser has been + # observed to crash with internal errors (e.g., IndexError from + # locale.translate_search) on certain query inputs. A parser bug + # should not bring down the whole search/consolidation pipeline — + # treat any failure as "no temporal constraint found" so the caller + # can fall back to non-temporal retrieval. + try: + results = self._search_dates(query, settings=settings) + except Exception as e: + logger.warning( + "dateparser raised %s on query (treating as no temporal constraint): %s", + type(e).__name__, + e, + ) + return QueryAnalysis(temporal_constraint=None) if not results: return QueryAnalysis(temporal_constraint=None) diff --git a/hindsight-api-slim/tests/test_query_analyzer.py b/hindsight-api-slim/tests/test_query_analyzer.py index 2f892ffb..bd2c4f8d 100644 --- a/hindsight-api-slim/tests/test_query_analyzer.py +++ b/hindsight-api-slim/tests/test_query_analyzer.py @@ -283,3 +283,37 @@ def test_query_analyzer_couple_weeks_ago(query_analyzer): assert analysis.temporal_constraint.end_date.month == 1 # Jan 8 (1 week before Jan 15) +def test_query_analyzer_dateparser_crash_returns_no_constraint(query_analyzer, monkeypatch, caplog): + """ + dateparser has been observed to crash with internal errors (e.g., + IndexError from locale.translate_search) on certain query inputs. + A parser bug should not propagate up the search/consolidation pipeline — + the analyzer should treat any failure as "no temporal constraint found". + """ + import logging + + reference_date = datetime(2025, 1, 15, 12, 0, 0) + + # Make sure the lazy loader has run so we can monkey-patch the cached call. + query_analyzer.load() + + def boom(*args, **kwargs): + raise IndexError("list index out of range") + + monkeypatch.setattr(query_analyzer, "_search_dates", boom) + + # Use a query that doesn't match any of the period regex patterns so the + # code path actually reaches the dateparser call. + query = "tell me what happened recently with the project" + + with caplog.at_level(logging.WARNING): + analysis = query_analyzer.analyze(query, reference_date) + + assert analysis.temporal_constraint is None, ( + "dateparser failures should be treated as no temporal constraint, not propagated" + ) + assert any("dateparser" in rec.message for rec in caplog.records), ( + "Should log a warning when dateparser fails" + ) + +