Skip to content

Commit 31066f8

Browse files
fix(daemon): accept Unicode session roots on Windows
1 parent d706c33 commit 31066f8

3 files changed

Lines changed: 50 additions & 8 deletions

File tree

‎src/daemon/application.c‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ static bool application_regular_db_exists(const char *project) {
373373
return stat(path, &status) == 0 && S_ISREG(status.st_mode);
374374
}
375375

376+
static bool application_canonical_directory_exists(const char *path) {
377+
cbm_path_info_t info = {0};
378+
return cbm_path_info_utf8(path, &info) == 0 && info.is_directory;
379+
}
380+
376381
static cbm_daemon_application_watch_t *application_find_watch_locked(
377382
cbm_daemon_application_t *application, const char *project) {
378383
for (cbm_daemon_application_watch_t *watch = application->watches; watch; watch = watch->next) {
@@ -2324,9 +2329,7 @@ static cbm_daemon_runtime_application_status_t application_set_context(
23242329
if (canonical && allowed_present) {
23252330
canonical = cbm_canonical_path(allowed, canonical_allowed, sizeof(canonical_allowed));
23262331
}
2327-
struct stat root_status;
2328-
canonical =
2329-
canonical && stat(canonical_root, &root_status) == 0 && S_ISDIR(root_status.st_mode);
2332+
canonical = canonical && application_canonical_directory_exists(canonical_root);
23302333
bool set =
23312334
canonical && cbm_mcp_server_set_session_context(session->mcp, canonical_root,
23322335
allowed_present ? canonical_allowed : NULL);
@@ -3339,9 +3342,8 @@ static int application_background_index(cbm_daemon_application_t *application,
33393342
return -1;
33403343
}
33413344
char canonical_root[APPLICATION_PATH_CAP];
3342-
struct stat root_status;
33433345
if (!cbm_canonical_path(root_path, canonical_root, sizeof(canonical_root)) ||
3344-
stat(canonical_root, &root_status) != 0 || !S_ISDIR(root_status.st_mode)) {
3346+
!application_canonical_directory_exists(canonical_root)) {
33453347
return -1;
33463348
}
33473349
yyjson_mut_doc *document = yyjson_mut_doc_new(NULL);

‎tests/test_daemon_application.c‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,45 @@ TEST(daemon_application_requires_immutable_explicit_context) {
713713
PASS();
714714
}
715715

716+
TEST(daemon_application_accepts_utf8_session_context_root) {
717+
char root[APP_TEST_PATH_CAP];
718+
snprintf(root, sizeof(root), "%s/cbm-app-context-caf\xC3\xA9-XXXXXX", cbm_tmpdir());
719+
bool root_ok = cbm_mkdtemp(root) != NULL;
720+
cbm_daemon_application_t *application = cbm_daemon_application_new(NULL);
721+
cbm_daemon_runtime_application_callbacks_t callbacks =
722+
cbm_daemon_application_runtime_callbacks(application);
723+
cbm_daemon_runtime_application_session_t *session = app_test_open(&callbacks, 311);
724+
uint8_t *context = NULL;
725+
uint32_t context_length = 0;
726+
uint8_t *response = NULL;
727+
uint32_t response_length = 0;
728+
bool context_ok = root_ok && app_test_context_request(root, root, &context, &context_length);
729+
cbm_daemon_runtime_application_status_t status =
730+
context_ok ? app_test_request(&callbacks, session, context, context_length, &response,
731+
&response_length)
732+
: CBM_DAEMON_RUNTIME_APPLICATION_TRANSPORT_ERROR;
733+
734+
free(context);
735+
free(response);
736+
if (session) {
737+
callbacks.session_close(callbacks.context, session);
738+
}
739+
bool stopped = application && cbm_daemon_application_shutdown(application, APP_TEST_TIMEOUT_MS);
740+
cbm_daemon_application_free(application);
741+
if (root_ok) {
742+
(void)cbm_rmdir(root);
743+
}
744+
745+
ASSERT_TRUE(root_ok);
746+
ASSERT_NOT_NULL(application);
747+
ASSERT_NOT_NULL(session);
748+
ASSERT_TRUE(context_ok);
749+
ASSERT_EQ(status, CBM_DAEMON_RUNTIME_APPLICATION_OK);
750+
ASSERT_EQ(response_length, 0);
751+
ASSERT_TRUE(stopped);
752+
PASS();
753+
}
754+
716755
TEST(daemon_application_mcp_notification_has_no_response) {
717756
cbm_daemon_application_t *application = cbm_daemon_application_new(NULL);
718757
cbm_daemon_runtime_application_callbacks_t callbacks =
@@ -5087,6 +5126,7 @@ SUITE(daemon_application) {
50875126
RUN_TEST(daemon_application_new_session_does_not_retain_initial_store);
50885127
RUN_TEST(daemon_application_request_cancel_is_scoped_to_exact_token);
50895128
RUN_TEST(daemon_application_requires_immutable_explicit_context);
5129+
RUN_TEST(daemon_application_accepts_utf8_session_context_root);
50905130
RUN_TEST(daemon_application_ui_config_updates_are_masked_and_serialized);
50915131
RUN_TEST(daemon_application_ui_config_rejects_noncanonical_frames);
50925132
RUN_TEST(daemon_application_ui_readiness_proof_is_generation_bound_before_context);

‎tests/windows/test_non_ascii_path.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,15 +247,15 @@ def make_fixture(root):
247247

248248

249249
def index_and_count(binary, repo, cache):
250-
"""Index `repo` into an isolated cache and return label-resolved counts."""
250+
"""Start in and index `repo`, then return label-resolved counts."""
251251
os.makedirs(cache, exist_ok=True)
252-
with McpServer(binary, cache_dir=cache) as s:
252+
with McpServer(binary, cache_dir=cache, cwd=repo) as s:
253253
s.initialize()
254254
resp = s.call_tool("index_repository", {"repo_path": repo}, timeout=180)
255255
index_txt, err = s.tool_text(resp)
256256
if err:
257257
return {"error": "index tools/call error: %r" % err}
258-
lp = s.call_tool("list_projects", {}, timeout=60)
258+
lp = s.call_tool("list_projects", {"include_details": True}, timeout=60)
259259
lp_txt, _ = s.tool_text(lp)
260260
projects = json.loads(lp_txt).get("projects") or []
261261
if not projects:

0 commit comments

Comments
 (0)