Skip to content

CrashWriter throws BAD_ARGUMENTS exception during crash when crash reporting is disabled #1437

@CarlosFelipeOR

Description

@CarlosFelipeOR

I checked the Altinity Stable Builds lifecycle table, and the Altinity Stable Build version I'm using is still supported.

Type of problem

Bug report - something's broken

Describe the situation

After PR #1387 disabled crash reporting by setting send_crash_reports.enabled=false and clearing the endpoint in embedded.xml, the crash report is no longer uploaded to crash.clickhouse.com (confirmed via tcpdump). However, the crash-report sending code path is still executed during a crash, resulting in a BAD_ARGUMENTS exception (Empty protocol in the URL).

This happens because CrashWriter::initialize() always creates a CrashWriter instance regardless of the enabled setting. When a crash occurs, CrashWriter::onSignal() checks if (instance) — which is always true — and proceeds to call sendError(), which tries to construct an HTTP request with an empty endpoint URL and throws.

This issue:


How to reproduce the behavior

Environment

Steps

  1. Start the ClickHouse server built from the PR Antalya 25.8 - disable crash report in more places #1387 branch
  2. Get the server PID and send a SIGSEGV:
kill -SEGV $(pidof clickhouse-server)
  1. Observe the server log output

Expected behavior

When crash reporting is disabled, the send path should be fully skipped — no send attempt and no exception:

<Information> CrashWriter: Not sending crash report

Actual behavior

The server attempts to send the crash report and fails with an exception:

2026.02.20 14:20:57.332260 [ 2316516 ] {} <Information> CrashWriter: Sending crash report
2026.02.20 14:20:57.335015 [ 2316516 ] {} <Information> CrashWriter: Cannot send a crash report: Code: 36. DB::Exception: Empty protocol in the URL. (BAD_ARGUMENTS), Stack trace
(when copying this message, always include the lines below):
0. ./ci/tmp/build/./src/Common/Exception.cpp:130: DB::Exception::Exception(DB::Exception::MessageMasked&&, int, bool) @ 0x00000000135db95f
1. DB::Exception::Exception(String&&, int, String, bool) @ 0x000000000ca82b8e
2. DB::Exception::Exception(PreformattedMessage&&, int) @ 0x000000000ca82640
3. DB::Exception::Exception<>(int, FormatStringHelperImpl<>) @ 0x000000000ca916eb
4. ./src/Common/ProxyConfiguration.h:34: DB::ProxyConfiguration::protocolFromString(String const&) @ 0x00000000139f53be
5. ./ci/tmp/build/./src/IO/WriteBufferFromHTTP.cpp:78: DB::BuilderWriteBufferFromHTTP::create() @ 0x00000000139f3fe3
6. ./ci/tmp/build/./src/Daemon/CrashWriter.cpp:77: CrashWriter::sendError(CrashWriter::Type, int, std::basic_string_view<char, std::char_traits<char>>, std::array<void*, 45ul>
   const&, unsigned long, unsigned long) @ 0x0000000016220d3a
7. ./src/Daemon/CrashWriter.cpp:54: SignalListener::onFault(int, siginfo_t const&, ucontext_t, StackTrace const&, std::vector<std::array<void, 45ul>,
   std::allocator<std::array<void, 45ul>>> const&, unsigned int, DB::ThreadStatus) const @ 0x0000000016217bf2
8. ./ci/tmp/build/./src/Common/SignalHandlers.cpp:335: SignalListener::run() @ 0x000000001620de3e
9. ./base/poco/Foundation/src/Thread_POSIX.cpp:341: Poco::ThreadImpl::runnableEntry(void*) @ 0x000000001f30b9c1
10. start_thread @ 0x000000000009caa4
11. clone3 @ 0x0000000000129c6c

(version 25.8.14.20001.altinityantalya)

Root cause analysis

In src/Daemon/CrashWriter.cpp, initialize() unconditionally creates the singleton instance:

void CrashWriter::initialize(Poco::Util::LayeredConfiguration & config)
{
    instance.reset(new CrashWriter(config));
}

The constructor skips setting the endpoint when enabled=false, leaving it as an empty string. But since instance is non-null, the guard in sendError() does not short-circuit:

void CrashWriter::sendError(...)
{
    if (!instance)  // always false — instance is always created
    {
        LOG_INFO(logger, "Not sending crash report");
        return;
    }
    // proceeds to use empty endpoint → exception
}

Suggested fix

Don't create the instance when crash reporting is disabled:

void CrashWriter::initialize(Poco::Util::LayeredConfiguration & config)
{
    if (config.getBool("send_crash_reports.enabled", false))
        instance.reset(new CrashWriter(config));
}

This way the existing if (instance) guards in onSignal() and onException() will correctly skip the send path.


Additional context

Related PR

Network verification

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions