From 56de845327dce03cdd1269b8aa73c6a1ecdb5cfa Mon Sep 17 00:00:00 2001 From: lorenzozanee Date: Sun, 20 Sep 2026 16:35:23 +0800 Subject: [PATCH 1/2] fix(cli): support non-ASCII Windows agent paths Signed-off-by: lorenzozanee --- src/cli/cli.c | 9 +++------ tests/test_cli.c | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index 51fbafc02..7cd59e0bf 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -19,6 +19,7 @@ #include "daemon/runtime.h" #include "daemon/version_cohort.h" #include "foundation/compat.h" +#include "foundation/compat_fs.h" #include "foundation/platform.h" #include "foundation/constants.h" #include "foundation/log.h" @@ -2600,12 +2601,8 @@ int cbm_remove_zed_mcp_owned(const char *binary_path, const char *config_path) { /* ── Agent detection ──────────────────────────────────────────── */ static bool dir_exists(const char *path) { - struct stat st; -#ifndef _WIN32 - return lstat(path, &st) == 0 && S_ISDIR(st.st_mode); -#else - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); -#endif + cbm_path_info_t info = {0}; + return cbm_path_info_utf8(path, &info) == CBM_PATH_INFO_OK && info.is_directory; } /* Resolve the Claude Code config dir. diff --git a/tests/test_cli.c b/tests/test_cli.c index 06ebb3841..b3c3c2f27 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -13074,6 +13074,24 @@ TEST(cli_detect_agents_finds_zed_in_roaming_appdata) { } #endif +#ifdef _WIN32 +TEST(cli_detect_agents_handles_non_ascii_windows_profile) { + char home[1024]; + snprintf(home, sizeof(home), "%s/cbm-用户-profile", cbm_tmpdir()); + test_rmdir_r(home); + char claude_dir[1024]; + snprintf(claude_dir, sizeof(claude_dir), "%s/.claude", home); + if (!test_mkdirp(claude_dir)) + FAIL("could not create non-ASCII Windows profile path"); + + cbm_detected_agents_t agents = cbm_detect_agents(home); + test_rmdir_r(home); + if (!agents.claude_code) + FAIL("Claude detection must support non-ASCII Windows profile paths"); + PASS(); +} +#endif + TEST(cli_detect_agents_finds_antigravity) { char tmpdir[256]; snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-detect-XXXXXX"); @@ -16258,6 +16276,7 @@ SUITE(cli) { #endif #ifdef _WIN32 RUN_TEST(cli_detect_agents_finds_zed_in_roaming_appdata); + RUN_TEST(cli_detect_agents_handles_non_ascii_windows_profile); #endif RUN_TEST(cli_detect_agents_finds_antigravity); RUN_TEST(cli_detect_agents_finds_kilocode); From c21c42b4d6ce88da41fe040d8a6254405bdfbeff Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 20 Oct 2026 19:30:41 +0200 Subject: [PATCH 2/2] test(cli): create the non-ASCII Windows profile through the wide API The Windows regression test for #2145 created its profile directory with test_mkdirp and read the result as a boolean. test_mkdirp returns 0 on success, so the test failed as soon as the directory existed and never reached the detection it was written to check. Inverting the check is not enough on its own. test_mkdirp uses cbm_mkdir, which is the narrow _mkdir on Windows. Under an ANSI code page such as 1252 it does not create the intended name: it creates a directory whose name is the UTF-8 bytes read as code-page characters. The old narrow stat finds that mangled directory and the new wide lookup correctly does not, so the test would pass without the fix and fail with it. th_mkdir_p goes through the wide API and creates the real name, the way a non-ASCII Windows profile actually looks. Since 2b329fe7, Claude Code is detected from settings.json, .claude.json or the claude CLI, through cbm_file_exists, which is already wide on Windows; a bare .claude directory no longer marks it as detected. The test now asserts Cursor, which is detected by its directory alone, so the assertion goes through dir_exists, the function this branch changes. Signed-off-by: Martin Vogel --- tests/test_cli.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_cli.c b/tests/test_cli.c index 42fba903e..c72095f22 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -14597,19 +14597,24 @@ TEST(cli_detect_agents_finds_zed_in_roaming_appdata) { #endif #ifdef _WIN32 +/* #2145: a profile path outside the ANSI code page must stay visible to agent + * detection. The directory is created with th_mkdir_p, which goes through the + * wide-char API: test_mkdirp uses cbm_mkdir, the narrow _mkdir on Windows, + * which would create a code-page-mangled name instead of this one. Cursor is + * detected by its directory alone, so the assertion goes through dir_exists. */ TEST(cli_detect_agents_handles_non_ascii_windows_profile) { char home[1024]; snprintf(home, sizeof(home), "%s/cbm-用户-profile", cbm_tmpdir()); test_rmdir_r(home); - char claude_dir[1024]; - snprintf(claude_dir, sizeof(claude_dir), "%s/.claude", home); - if (!test_mkdirp(claude_dir)) + char cursor_dir[1024]; + snprintf(cursor_dir, sizeof(cursor_dir), "%s/.cursor", home); + if (th_mkdir_p(cursor_dir) != 0) FAIL("could not create non-ASCII Windows profile path"); cbm_detected_agents_t agents = cbm_detect_agents(home); test_rmdir_r(home); - if (!agents.claude_code) - FAIL("Claude detection must support non-ASCII Windows profile paths"); + if (!agents.cursor) + FAIL("agent detection must support non-ASCII Windows profile paths"); PASS(); } #endif