From 32272f95e339046d12264511703314f407a99844 Mon Sep 17 00:00:00 2001 From: Gaurav Dubey Date: Fri, 23 Oct 2026 18:15:46 +0000 Subject: [PATCH] fix(daemon): log the effective auto_index_limit in daemon.autoindex.skipped When daemon-backed auto-indexing refuses a repository, the daemon.autoindex.skipped warning reported the tracked-file count but not the limit that caused the refusal. The MCP autoindex.skip event already carries limit= (#1466); add the same field to the daemon warning, using the auto_index_limit value the admission check already used. Admission behavior is unchanged. daemon_application_auto_index_honors_tracked_file_limit now captures the warning through a log sink and asserts reason=too_many_files, files=1 and limit=0 as whole fields. Fixes #1818 Signed-off-by: Gaurav Dubey --- src/daemon/application.c | 4 ++- tests/test_daemon_application.c | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/daemon/application.c b/src/daemon/application.c index 32b407743c..a7beac6855 100644 --- a/src/daemon/application.c +++ b/src/daemon/application.c @@ -2398,10 +2398,12 @@ static void application_background_initialize_impl(cbm_daemon_application_sessio cbm_mcp_auto_index_within_file_limit(root_path, auto_index_limit, &tracked_files); if (auto_index_candidate && !within_auto_index_limit) { char files[32]; + char limit[32]; (void)snprintf(files, sizeof(files), "%d", tracked_files); + (void)snprintf(limit, sizeof(limit), "%d", auto_index_limit); cbm_log_warn("daemon.autoindex.skipped", "project", project, "reason", tracked_files >= 0 ? "too_many_files" : "unsafe_or_unavailable_path", "files", - files); + files, "limit", limit); } bool args_required = auto_index_candidate && within_auto_index_limit; char *args = args_required ? application_auto_index_args(application, root_path) : NULL; diff --git a/tests/test_daemon_application.c b/tests/test_daemon_application.c index a923043ca4..ea91d78ca0 100644 --- a/tests/test_daemon_application.c +++ b/tests/test_daemon_application.c @@ -12,6 +12,7 @@ #include "foundation/compat.h" #include "foundation/compat_fs.h" #include "foundation/compat_thread.h" +#include "foundation/log.h" #include "foundation/platform.h" #include "foundation/sha256.h" #include "foundation/subprocess.h" @@ -2884,6 +2885,38 @@ TEST(daemon_application_programmatic_index_forwards_set_doc_candidates) { ASSERT_EQ(garbage, -1); PASS(); } + +/* #1818: the daemon.autoindex.skipped warning must name the effective limit, + * like the MCP autoindex.skip event does (#1466). The warning is emitted on the + * background-initialize thread, before the release increment of the + * background-initialize counter that the test waits for, so the captured line + * is complete once that wait returns. */ +static char app_autoindex_skipped_line[1024]; +static atomic_bool app_autoindex_skipped_seen = ATOMIC_VAR_INIT(false); + +static void app_autoindex_skipped_sink(const char *line) { + if (!line || !strstr(line, "msg=daemon.autoindex.skipped") || + atomic_load_explicit(&app_autoindex_skipped_seen, memory_order_acquire)) { + return; + } + (void)snprintf(app_autoindex_skipped_line, sizeof(app_autoindex_skipped_line), "%s", line); + atomic_store_explicit(&app_autoindex_skipped_seen, true, memory_order_release); +} + +/* True when `line` carries the whole key=value field `field` (not a prefix of + * a longer value such as limit=00). */ +static bool app_log_line_has_field(const char *line, const char *field) { + size_t length = strlen(field); + for (const char *at = strstr(line, field); at; at = strstr(at + 1, field)) { + bool starts = at == line || at[-1] == ' '; + char next = at[length]; + if (starts && (next == '\0' || next == ' ' || next == '\n' || next == '\r')) { + return true; + } + } + return false; +} + TEST(daemon_application_auto_index_honors_tracked_file_limit) { app_env_backup_t cache_environment; bool cache_saved = app_env_backup_capture(&cache_environment, "CBM_CACHE_DIR"); @@ -2928,6 +2961,13 @@ TEST(daemon_application_auto_index_honors_tracked_file_limit) { cbm_daemon_application_runtime_callbacks(application); cbm_daemon_runtime_application_session_t *session = application ? app_test_open(&callbacks, 4190) : NULL; + app_autoindex_skipped_line[0] = '\0'; + atomic_store_explicit(&app_autoindex_skipped_seen, false, memory_order_release); + CBMLogLevel previous_log_level = cbm_log_get_level(); + CBMLogFormat previous_log_format = cbm_log_get_format(); + cbm_log_set_level(CBM_LOG_INFO); + cbm_log_set_format(CBM_LOG_FORMAT_TEXT); + cbm_log_set_sink_ex(app_autoindex_skipped_sink, CBM_LOG_SINK_REPLACE); int bg_baseline = cbm_daemon_application_background_initializes_for_test(); bool initialized = app_test_initialize_profile(&callbacks, session, root, CBM_MCP_TOOL_PROFILE_ALL, NULL, NULL); @@ -2935,6 +2975,11 @@ TEST(daemon_application_auto_index_honors_tracked_file_limit) { bool limit_prevented_admission = initialized && admission_evaluated && atomic_load(&fake.starts) == 0 && cbm_daemon_application_active_jobs(application) == 0; + cbm_log_set_sink(NULL); + cbm_log_set_format(previous_log_format); + cbm_log_set_level(previous_log_level); + bool skip_logged = admission_evaluated && + atomic_load_explicit(&app_autoindex_skipped_seen, memory_order_acquire); if (session) { callbacks.session_cancel(callbacks.context, session); @@ -2954,6 +2999,10 @@ TEST(daemon_application_auto_index_honors_tracked_file_limit) { ASSERT_TRUE(config_ready); ASSERT_TRUE(initialized); ASSERT_TRUE(limit_prevented_admission); + ASSERT_TRUE(skip_logged); + ASSERT_TRUE(app_log_line_has_field(app_autoindex_skipped_line, "reason=too_many_files")); + ASSERT_TRUE(app_log_line_has_field(app_autoindex_skipped_line, "files=1")); + ASSERT_TRUE(app_log_line_has_field(app_autoindex_skipped_line, "limit=0")); ASSERT_TRUE(stopped); ASSERT_TRUE(cache_restored); PASS();