From 58f6823e425df013874f79fb5c029a28b93e5c80 Mon Sep 17 00:00:00 2001 From: Sam Bonifacio Date: Wed, 6 Aug 2025 23:56:12 +1000 Subject: [PATCH] fix(fzf): improve exit code handling with centralized error processing - Extract fzf error handling into reusable _handle_fzf_error() helper - Distinguish between exit code 1 (no matches) and actual errors - Return consistent dict structure: {"matches": []} vs {"error": "..."} - Apply to both multiline and standard filter modes - Update test to expect empty matches instead of error for no results --- mcp_fd_server.py | 22 ++++++++++++++-------- tests/test_fd_server.py | 5 +++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/mcp_fd_server.py b/mcp_fd_server.py index 4a192fa..280b849 100755 --- a/mcp_fd_server.py +++ b/mcp_fd_server.py @@ -180,6 +180,18 @@ def _suggest_fuzzy_terms(regex_pattern: str) -> str: return fuzzy +def _handle_fzf_error(exc: subprocess.CalledProcessError, warnings: list[str]) -> dict[str, Any]: + """Handle fzf CalledProcessError, returning appropriate result dict.""" + # fzf returns exit code 1 when no matches found - this is not an error + if exc.returncode == 1: + return {"matches": []} # No matches found, return empty list + + error_result: dict[str, Any] = {"error": str(exc)} + if warnings: + error_result["warnings"] = warnings + return error_result + + # --------------------------------------------------------------------------- # FastMCP server instance # --------------------------------------------------------------------------- @@ -355,10 +367,7 @@ def filter_files( matches.append(chunk.decode("utf-8", errors="replace")) except subprocess.CalledProcessError as exc: - error_result: dict[str, Any] = {"error": str(exc)} - if warnings: - error_result["warnings"] = warnings - return error_result + return _handle_fzf_error(exc, warnings) else: # Standard mode - file paths only fd_cmd: list[str] = [fd_bin, *shlex.split(fd_flags), pattern, search_path] @@ -374,10 +383,7 @@ def filter_files( fd_proc.wait() matches = [_normalize_path(p) for p in out.splitlines() if p] except subprocess.CalledProcessError as exc: - error_result: dict[str, Any] = {"error": str(exc)} - if warnings: - error_result["warnings"] = warnings - return error_result + return _handle_fzf_error(exc, warnings) if first and matches: matches = matches[:1] diff --git a/tests/test_fd_server.py b/tests/test_fd_server.py index 76b136b..ddf5d00 100644 --- a/tests/test_fd_server.py +++ b/tests/test_fd_server.py @@ -521,8 +521,9 @@ def test_fd_flag_handling(tmp_path: Path): result = mcp_fd_server.filter_files("nomatch") - # Should handle empty results gracefully - assert "error" in result + # Should handle empty results gracefully (fzf exit code 1 = no matches, not error) + assert "matches" in result + assert result["matches"] == [] def test_multiline_support(tmp_path: Path):