From 8a2d808d925c7884e2ba25fe70d5f8130a9a03aa Mon Sep 17 00:00:00 2001 From: Matt Liberty Date: Sun, 19 Apr 2026 14:18:26 +0000 Subject: [PATCH] run-clang-tidy-cached: surface fatal clang-tidy errors instead of caching empty output Previously, when clang-tidy exited non-zero without producing any check findings (e.g. bad config, missing compile db, unknown check), stderr was discarded via `2>/dev/null` and an empty output file was cached as if the file had no findings. Subsequent runs would hit the cache and never notice. Redirect stderr into the output, detect non-zero exits with no findings as fatal, stop other worker threads via an atomic flag, and print the captured output before aborting the run. Signed-off-by: Matt Liberty --- etc/run-clang-tidy-cached.cc | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/etc/run-clang-tidy-cached.cc b/etc/run-clang-tidy-cached.cc index 4953ee8b0fb..d5694c33e36 100755 --- a/etc/run-clang-tidy-cached.cc +++ b/etc/run-clang-tidy-cached.cc @@ -39,6 +39,7 @@ B=${0%%.cc}; [ "$B" -nt "$0" ] || c++ -std=c++17 -o"$B" "$0" && exec "$B" "$@"; #include #include +#include #include #include #include @@ -308,8 +309,14 @@ class ClangTidyRunner const std::string uniquifier = "." + std::to_string(getpid()); std::mutex queue_access_lock; + std::atomic fatal_stop{false}; + std::string fatal_output; + std::string fatal_file; auto clang_tidy_runner = [&]() { for (;;) { + if (fatal_stop.load()) { + return; + } filepath_contenthash_t work; { const std::lock_guard lock(queue_access_lock); @@ -328,7 +335,7 @@ class ClangTidyRunner // it is easy to find with `ps` or `top`. const std::string command = clang_tidy_ + " '" + work.first.string() + "'" + clang_tidy_args_ + "> '" + tmp_out - + "' 2>/dev/null"; + + "' 2>&1"; const int r = system(command.c_str()); #ifdef WIFSIGNALED // NOLINTBEGIN @@ -340,6 +347,30 @@ class ClangTidyRunner } // NOLINTEND #endif + // Detect systemic clang-tidy failure (bad config, missing compile db, + // etc.): non-zero exit with no check findings in output. Abort the + // run and surface the error instead of silently caching empty output. +#ifdef WIFEXITED + // NOLINTBEGIN + if (WIFEXITED(r) && WEXITSTATUS(r) != 0) { + // NOLINTEND +#else + if (r != 0) { +#endif + const std::string out = GetContent(tmp_out); + static const std::regex check_finding( + R"(\[[a-zA-Z0-9.]+-[a-zA-Z0-9.-]+\])"); + if (!std::regex_search(out, check_finding)) { + bool expected = false; + if (fatal_stop.compare_exchange_strong(expected, true)) { + fatal_output = out; + fatal_file = work.first.string(); + } + std::error_code ignored_error; + fs::remove(tmp_out, ignored_error); + return; + } + } const std::string filter_filename = work.first.filename().string(); RepairFilenameOccurences(filter_filename, tmp_out, tmp_out); fs::rename(tmp_out, final_out); // atomic replacement @@ -356,6 +387,12 @@ class ClangTidyRunner if (print_progress) { fprintf(stderr, " \n"); // Clean out progress counter. } + if (fatal_stop.load()) { + std::cerr << "\nclang-tidy failed on " << fatal_file + << " (aborting; output below):\n" + << fatal_output << "\n"; + exit(EXIT_FAILURE); + } } private: