From 4301f785037e09df35b5201a2abdb12be465d0aa Mon Sep 17 00:00:00 2001 From: Jessica Mulein Date: Thu, 11 Jun 2026 09:05:19 -0700 Subject: [PATCH] fix: quote search queries to prevent FTS5 special-char crashes Cymbal's SQLite FTS5 index interprets hyphens, plus signs, and other special characters as query operators. When users search for terms like 'vault-store' or 'home-entry', the unquoted hyphen is parsed as a NOT operator, causing 'no such column' SQL errors. Fix: wrap all search/investigate/find_references queries in double quotes when they contain FTS5 special characters (-+*~). Quoted terms are treated as literal phrase matches by FTS5, preventing the SQL interpretation. Affects: search(), investigate(), find_references() in CymbalSubprocess. --- python/cymbal/__init__.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/python/cymbal/__init__.py b/python/cymbal/__init__.py index 93599a2..46d544b 100644 --- a/python/cymbal/__init__.py +++ b/python/cymbal/__init__.py @@ -124,22 +124,29 @@ def index(self, repo_path: str) -> Dict[str, Any]: return self._run_command(["index", repo_path]) def search(self, query: str, limit: int = 20) -> List[Dict[str, Any]]: - """Search for symbols.""" - return self._run_command(["search", query, "--limit", str(limit)]) + """Search for symbols. + + Queries are quoted to prevent FTS5 interpretation of special characters + (e.g. hyphens treated as NOT operator causing SQL errors). + """ + safe_query = f'"{query}"' if query and not query.startswith('"') else query + return self._run_command(["search", safe_query, "--limit", str(limit)]) def investigate(self, symbol_name: str, file_hint: str = "") -> Dict[str, Any]: """Investigate a symbol.""" - + # Quote if contains special FTS5 characters + safe_name = f'"{symbol_name}"' if symbol_name and any(c in symbol_name for c in '-+*~') else symbol_name if file_hint: - symbol_name = f"{file_hint}:{symbol_name}" + safe_name = f"{file_hint}:{safe_name}" - args = ["investigate", symbol_name] + args = ["investigate", safe_name] return self._run_command(args) def find_references(self, symbol_name: str, limit: int = 50) -> List[Dict[str, Any]]: """Find references to a symbol.""" - return self._run_command(["refs", symbol_name, "--limit", str(limit)]) + safe_name = f'"{symbol_name}"' if symbol_name and any(c in symbol_name for c in '-+*~') else symbol_name + return self._run_command(["refs", safe_name, "--limit", str(limit)]) def close(self): """Clean up resources."""