From 0c48e756ef598fcf7f31202dbc2cdb86681f155e Mon Sep 17 00:00:00 2001 From: lg320531124 Date: Mon, 29 Jun 2026 17:10:18 +0800 Subject: [PATCH 1/2] fix(search): use NUL-separated filelist + xargs -0 for paths with spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a repo path contains spaces (e.g. /Users/name/My Project/), the scoped search pipeline breaks because: 1. write_scoped_filelist() writes newline-separated paths → xargs splits on spaces, passing truncated paths to grep 2. xargs has no -0 flag, so it can't handle NUL separators Fix: write NUL-separated paths in write_scoped_filelist() via fwrite(), and add -0 flag to xargs in build_grep_cmd(). Non-scoped mode was already safe (root_path is single-quoted in the grep command). Closes #687 Signed-off-by: lg320531124 --- src/mcp/mcp.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index 8d8810896..d300d1cfa 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -3821,10 +3821,10 @@ static void build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bool scoped const char *flag = use_regex ? "-E" : "-F"; if (scoped) { if (file_pattern) { - snprintf(cmd, cmd_sz, "xargs grep -Hn %s --include='%s' -f '%s' < '%s' 2>/dev/null", + snprintf(cmd, cmd_sz, "xargs -0 grep -Hn %s --include='%s' -f '%s' < '%s' 2>/dev/null", flag, file_pattern, tmpfile, filelist); } else { - snprintf(cmd, cmd_sz, "xargs grep -Hn %s -f '%s' < '%s' 2>/dev/null", flag, tmpfile, + snprintf(cmd, cmd_sz, "xargs -0 grep -Hn %s -f '%s' < '%s' 2>/dev/null", flag, tmpfile, filelist); } } else { @@ -4240,10 +4240,19 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co bool ok = false; if (fl) { for (int fi = 0; fi < indexed_count; fi++) { - /* Use forward slashes so xargs doesn't interpret Windows - * backslashes as escape sequences (e.g. \n becomes newline). - * Binary mode to prevent CRLF (xargs would see trailing \r). */ - (void)fprintf(fl, "%s/%s\n", root_path, indexed_files[fi]); + /* Use NUL-separated output so xargs -0 handles paths with + * spaces correctly. Binary mode prevents CRLF on Windows. */ + size_t rlen = strlen(root_path); + size_t flen = strlen(indexed_files[fi]); + char buf[CBM_SZ_2K]; + size_t total = 0; + memcpy(buf + total, root_path, rlen); + total += rlen; + buf[total++] = '/'; + memcpy(buf + total, indexed_files[fi], flen); + total += flen; + buf[total++] = '\0'; /* NUL separator for xargs -0 */ + (void)fwrite(buf, 1, total, fl); } (void)fclose(fl); ok = true; From f2ff6305d116e1ee2e5529b2aa824c5ac888bd72 Mon Sep 17 00:00:00 2001 From: lg320531124 Date: Mon, 29 Jun 2026 21:13:13 +0800 Subject: [PATCH 2/2] fix(search): line-separate filelist on Windows for PowerShell Get-Content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NUL-separated filelist from the previous commit breaks the Windows search path: PowerShell 'Get-Content | ForEach-Object { Select-String -LiteralPath src/mcp/mcp.c }' reads line-by-line and rejects NUL bytes with 'Illegal characters in path', making search_code return empty and failing test_mcp.c:1098 (search_code_multi_word ASSERT HandleRequest). Windows never needed NUL — Get-Content feeds each path as one line to -LiteralPath, which accepts spaces without shell word-splitting. NUL was only needed on Unix for 'xargs -0' to survive spaces/newlines in paths. Branch on _WIN32: '\n' on Windows (PowerShell line reader), '\0' on Unix (xargs -0). Fixes the CI regression introduced while fixing #687. Signed-off-by: lg320531124 --- src/mcp/mcp.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mcp/mcp.c b/src/mcp/mcp.c index d300d1cfa..8ca146415 100644 --- a/src/mcp/mcp.c +++ b/src/mcp/mcp.c @@ -4240,8 +4240,14 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co bool ok = false; if (fl) { for (int fi = 0; fi < indexed_count; fi++) { - /* Use NUL-separated output so xargs -0 handles paths with - * spaces correctly. Binary mode prevents CRLF on Windows. */ + /* Build "/". Separator differs by platform: + * - Unix: NUL-separated, consumed by `xargs -0` (handles + * spaces in paths; newline would split on \n in paths). + * - Windows: newline-separated, consumed by PowerShell + * `Get-Content | ForEach-Object { Select-String -LiteralPath $_ }` + * which reads line-by-line and accepts spaces in each path; + * NUL bytes break Get-Content ("Illegal characters in path"). + * Binary mode prevents CRLF translation on Windows. */ size_t rlen = strlen(root_path); size_t flen = strlen(indexed_files[fi]); char buf[CBM_SZ_2K]; @@ -4251,7 +4257,11 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co buf[total++] = '/'; memcpy(buf + total, indexed_files[fi], flen); total += flen; +#ifdef _WIN32 + buf[total++] = '\n'; /* line-separated for PowerShell Get-Content */ +#else buf[total++] = '\0'; /* NUL separator for xargs -0 */ +#endif (void)fwrite(buf, 1, total, fl); } (void)fclose(fl);