Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/mcp/mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -11658,10 +11658,12 @@ static void maybe_auto_index(cbm_mcp_server_t *srv) {
int file_count = -1;
if (!cbm_mcp_auto_index_within_file_limit(srv->session_root, file_limit, &file_count)) {
char files[32];
char limit[32];
(void)snprintf(files, sizeof(files), "%d", file_count);
(void)snprintf(limit, sizeof(limit), "%d", file_limit);
cbm_log_warn("autoindex.skip", "reason",
file_count >= 0 ? "too_many_files" : "unsafe_or_unavailable_path", "files",
files, "limit", CBM_CONFIG_AUTO_INDEX_LIMIT);
files, "limit", limit);
return;
}

Expand Down
100 changes: 100 additions & 0 deletions tests/test_mcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -10540,6 +10540,105 @@ TEST(mcp_auto_watch_false_skips_watcher_on_connect) {
PASS();
}

/* ══════════════════════════════════════════════════════════════════
* #1466 — autoindex.skip must report the effective numeric limit
* ══════════════════════════════════════════════════════════════════ */

static char autoindex_skip_log[1024];

/* Keeps only the too_many_files skip line, so later lines cannot displace it. */
static void autoindex_skip_capture_log(const char *line) {
if (line && strstr(line, "msg=autoindex.skip") && strstr(line, "reason=too_many_files")) {
snprintf(autoindex_skip_log, sizeof(autoindex_skip_log), "%s", line);
}
}

/* Drive initialize → maybe_auto_index over a fresh project holding more tracked
* files than auto_index_limit, and capture the resulting skip warning.
* Returns false on fixture setup failure. */
static bool autoindex_skip_warning(char *out, size_t out_size) {
char cache[256];
snprintf(cache, sizeof(cache), "%s/cbm-autoindex-limit-XXXXXX", cbm_tmpdir());
if (!cbm_mkdtemp(cache)) {
return false;
}

char repodir[512];
snprintf(repodir, sizeof(repodir), "%s/repo", cache);
char file_a[640];
char file_b[640];
snprintf(file_a, sizeof(file_a), "%s/a.py", repodir);
snprintf(file_b, sizeof(file_b), "%s/b.py", repodir);
if (th_mkdir_p(repodir) != 0 || th_write_file(file_a, "def a():\n return 1\n") != 0 ||
th_write_file(file_b, "def b():\n return 2\n") != 0) {
th_rmtree(cache);
return false;
}

const char *saved = getenv("CBM_CACHE_DIR");
char *saved_copy = saved ? strdup(saved) : NULL;
cbm_setenv("CBM_CACHE_DIR", cache, 1);

char old_cwd[1024];
if (!cbm_getcwd(old_cwd, sizeof(old_cwd)) || cbm_chdir(repodir) != 0) {
restore_cache_dir(saved_copy);
free(saved_copy);
th_rmtree(cache);
return false;
}

bool ok = false;
cbm_config_t *cfg = cbm_config_open(cache);
if (cfg) {
cbm_config_set(cfg, CBM_CONFIG_AUTO_INDEX, "true");
cbm_config_set(cfg, CBM_CONFIG_AUTO_INDEX_LIMIT, "1");

cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
if (srv) {
autoindex_skip_log[0] = '\0';
CBMLogLevel prev_level = cbm_log_get_level();
cbm_log_set_level(CBM_LOG_WARN);
cbm_log_set_format(CBM_LOG_FORMAT_TEXT);
cbm_log_set_sink_ex(autoindex_skip_capture_log, CBM_LOG_SINK_REPLACE);

cbm_mcp_server_set_config(srv, cfg);
char *resp = cbm_mcp_server_handle(
srv, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}}");
free(resp);
cbm_mcp_server_free(srv);

cbm_log_set_sink(NULL);
cbm_log_set_level(prev_level);

snprintf(out, out_size, "%s", autoindex_skip_log);
ok = true;
}
cbm_config_close(cfg);
}

(void)cbm_chdir(old_cwd);
restore_cache_dir(saved_copy);
free(saved_copy);
th_rmtree(cache);
return ok;
}

/* RED before the fix: the warning carries `limit=auto_index_limit`, the config
* key constant, instead of the configured value. */
TEST(autoindex_skip_reports_numeric_limit_issue1466) {
char warning[1024];
if (!autoindex_skip_warning(warning, sizeof(warning))) {
PASS(); /* fixture setup failed (tmpdir/cwd unavailable) — skip */
}
/* Not vacuous: the skip path must actually have been taken. */
ASSERT_NOT_NULL(strstr(warning, "msg=autoindex.skip"));
ASSERT_NOT_NULL(strstr(warning, "reason=too_many_files"));
ASSERT_NOT_NULL(strstr(warning, "files=2"));
ASSERT_NOT_NULL(strstr(warning, "limit=1"));
ASSERT_NULL(strstr(warning, "limit=auto_index_limit"));
PASS();
}

/* ══════════════════════════════════════════════════════════════════
* #853 — auto_watch=false must ALSO gate the SUPERVISED fresh-index
* watcher registration (keystone × #849 merge interaction)
Expand Down Expand Up @@ -11472,6 +11571,7 @@ SUITE(mcp) {
RUN_TEST(mcp_auto_watch_default_registers_watcher_on_connect);
RUN_TEST(mcp_auto_watch_false_skips_watcher_on_connect);
RUN_TEST(mcp_auto_watch_false_skips_supervised_autoindex_issue853);
RUN_TEST(autoindex_skip_reports_numeric_limit_issue1466);
}

/* Kept separate so daemon-coordination regressions can be iterated without
Expand Down
Loading