@@ -4125,6 +4125,144 @@ TEST(readonly_query_succeeds_on_readonly_fs) {
41254125
41264126#undef ROQ_PROJECT
41274127
4128+ /* ══════════════════════════════════════════════════════════════════
4129+ * #823 — CLI/supervised index_repository must preserve name override
4130+ * ══════════════════════════════════════════════════════════════════ */
4131+
4132+ enum {
4133+ IDX823_OK = 0 ,
4134+ IDX823_NO_SERVER = 61 ,
4135+ IDX823_NO_RESULT = 62 ,
4136+ IDX823_NOT_INDEXED = 63 ,
4137+ IDX823_RESPONSE_NAME_MISSING = 64 ,
4138+ IDX823_LIST_NAME_MISSING = 65 ,
4139+ IDX823_SEARCH_FAILED = 66 ,
4140+ };
4141+
4142+ #ifndef _WIN32 /* helper used only by the POSIX fork harness below */
4143+ static int idx823_supervised_name_override_check (const char * repo_dir , const char * custom_name ) {
4144+ /* Match the real CLI/MCP server state: a marked host with the supervisor
4145+ * enabled. The worker receives the same args JSON the CLI forwards. */
4146+ cbm_index_supervisor_mark_host ();
4147+ cbm_unsetenv ("CBM_INDEX_SUPERVISOR" );
4148+ cbm_setenv ("CBM_INDEX_MAX_RESTARTS" , "1" , 1 );
4149+ cbm_setenv ("CBM_INDEX_WORKER_TIMEOUT_S" , "30" , 1 );
4150+
4151+ cbm_mcp_server_t * srv = cbm_mcp_server_new (NULL );
4152+ if (!srv ) {
4153+ return IDX823_NO_SERVER ;
4154+ }
4155+
4156+ char args [1024 ];
4157+ snprintf (args , sizeof (args ), "{\"repo_path\":\"%s\",\"mode\":\"fast\",\"name\":\"%s\"}" ,
4158+ repo_dir , custom_name );
4159+ char * resp = cbm_mcp_handle_tool (srv , "index_repository" , args );
4160+ int code = IDX823_OK ;
4161+ if (!resp ) {
4162+ code = IDX823_NO_RESULT ;
4163+ } else if (!response_contains_json_fragment (resp , "\"status\":\"indexed\"" )) {
4164+ code = IDX823_NOT_INDEXED ;
4165+ } else {
4166+ char expected [256 ];
4167+ snprintf (expected , sizeof (expected ), "\"project\":\"%s\"" , custom_name );
4168+ if (!response_contains_json_fragment (resp , expected )) {
4169+ code = IDX823_RESPONSE_NAME_MISSING ;
4170+ }
4171+ }
4172+ free (resp );
4173+
4174+ if (code == IDX823_OK ) {
4175+ char * projects = cbm_mcp_handle_tool (srv , "list_projects" , "{}" );
4176+ char expected [256 ];
4177+ snprintf (expected , sizeof (expected ), "\"name\":\"%s\"" , custom_name );
4178+ if (!projects || !response_contains_json_fragment (projects , expected )) {
4179+ code = IDX823_LIST_NAME_MISSING ;
4180+ }
4181+ free (projects );
4182+ }
4183+
4184+ if (code == IDX823_OK ) {
4185+ char q [512 ];
4186+ snprintf (q , sizeof (q ),
4187+ "{\"project\":\"%s\",\"name_pattern\":\"idx823_fn\",\"label\":\"Function\"}" ,
4188+ custom_name );
4189+ char * sr = cbm_mcp_handle_tool (srv , "search_graph" , q );
4190+ if (!sr || !strstr (sr , "idx823_fn" )) {
4191+ code = IDX823_SEARCH_FAILED ;
4192+ }
4193+ free (sr );
4194+ }
4195+
4196+ cbm_mcp_server_free (srv );
4197+ return code ;
4198+ }
4199+ #endif
4200+
4201+ TEST (index_repository_cli_name_override_issue823 ) {
4202+ #ifdef _WIN32
4203+ SKIP_PLATFORM ("POSIX fork harness required to isolate supervisor host mark" );
4204+ #else
4205+ char tmp_dir [256 ];
4206+ snprintf (tmp_dir , sizeof (tmp_dir ), "/tmp/cbm-idx823-repo-XXXXXX" );
4207+ if (!cbm_mkdtemp (tmp_dir )) {
4208+ FAIL ("cbm_mkdtemp repo failed" );
4209+ }
4210+ char cache [256 ];
4211+ snprintf (cache , sizeof (cache ), "/tmp/cbm-idx823-cache-XXXXXX" );
4212+ if (!cbm_mkdtemp (cache )) {
4213+ th_rmtree (tmp_dir );
4214+ FAIL ("cbm_mkdtemp cache failed" );
4215+ }
4216+
4217+ const char * saved_cache = getenv ("CBM_CACHE_DIR" );
4218+ char * saved_cache_copy = saved_cache ? strdup (saved_cache ) : NULL ;
4219+ cbm_setenv ("CBM_CACHE_DIR" , cache , 1 );
4220+
4221+ char src_path [512 ];
4222+ snprintf (src_path , sizeof (src_path ), "%s/main.py" , tmp_dir );
4223+ ASSERT_EQ (th_write_file (src_path , "def idx823_fn():\n return 823\n" ), 0 );
4224+
4225+ const char * custom_name = "issue823-custom-project" ;
4226+ int code = -1 ;
4227+ bool signalled = false;
4228+ int sig = 0 ;
4229+
4230+ fflush (NULL );
4231+ pid_t pid = fork ();
4232+ if (pid == 0 ) {
4233+ alarm (60 );
4234+ _exit (idx823_supervised_name_override_check (tmp_dir , custom_name ));
4235+ }
4236+ ASSERT_TRUE (pid > 0 );
4237+ int status = 0 ;
4238+ (void )waitpid (pid , & status , 0 );
4239+ if (WIFEXITED (status )) {
4240+ code = WEXITSTATUS (status );
4241+ } else if (WIFSIGNALED (status )) {
4242+ signalled = true;
4243+ sig = WTERMSIG (status );
4244+ }
4245+
4246+ char * path_project = cbm_project_name_from_path (tmp_dir );
4247+ cleanup_project_db (cache , custom_name );
4248+ cleanup_project_db (cache , path_project );
4249+ free (path_project );
4250+ restore_cache_dir (saved_cache_copy );
4251+ free (saved_cache_copy );
4252+ th_rmtree (cache );
4253+ th_rmtree (tmp_dir );
4254+
4255+ if (signalled ) {
4256+ printf (" child killed by signal %d (alarm => worker hang)\n" , sig );
4257+ } else if (code != IDX823_OK ) {
4258+ printf (" child exit code %d (64=response name, 65=list name, 66=search)\n" , code );
4259+ }
4260+ ASSERT_FALSE (signalled );
4261+ ASSERT_EQ (code , IDX823_OK );
4262+ PASS ();
4263+ #endif
4264+ }
4265+
41284266/* ══════════════════════════════════════════════════════════════════
41294267 * #845 — supervisor gate must not wrap embedders of cbm_mcp_handle_tool
41304268 * ══════════════════════════════════════════════════════════════════ */
@@ -5083,6 +5221,7 @@ SUITE(mcp) {
50835221 RUN_TEST (tool_manage_adr_unified_backend_issue256 );
50845222 RUN_TEST (tool_index_repository_reports_store_backed_adr );
50855223 RUN_TEST (tool_index_repository_dot_uses_absolute_project_key_and_preserves_adr );
5224+ RUN_TEST (index_repository_cli_name_override_issue823 );
50865225 RUN_TEST (index_supervisor_gate_requires_marked_host_issue845 );
50875226 RUN_TEST (index_bg_paths_route_through_supervisor_issue832 );
50885227 RUN_TEST (index_recovery_parallel_quarantines_crasher );
0 commit comments