diff --git a/.changeset/tidy-mcp-lifecycle.md b/.changeset/tidy-mcp-lifecycle.md new file mode 100644 index 0000000..0714c86 --- /dev/null +++ b/.changeset/tidy-mcp-lifecycle.md @@ -0,0 +1,5 @@ +--- +"posthog-ruby": patch +--- + +Fix instance tracking for client subclasses so `PostHog::MCP::Client` can initialize outside test mode and shut down without raising. Clients that skip registration no longer remove another client's duplicate-instance warning on shutdown. diff --git a/lib/posthog/client.rb b/lib/posthog/client.rb index 73e21b7..17eae40 100644 --- a/lib/posthog/client.rb +++ b/lib/posthog/client.rb @@ -187,7 +187,8 @@ def initialize(opts = {}) # Warn when multiple clients are created with the same API key (can cause dropped events) unless @disabled || opts[:test_mode] || opts[:disable_singleton_warning] - previous_count = self.class._increment_instance_count(@api_key) + previous_count = PostHog::Client._increment_instance_count(@api_key) + @instance_registered = true if previous_count >= 1 logger.warn( 'Multiple PostHog client instances detected for the same API key. ' \ @@ -947,7 +948,7 @@ def shutdown(timeout: nil) flushed = false begin - self.class._decrement_instance_count(@api_key) unless @disabled + PostHog::Client._decrement_instance_count(@api_key) if @instance_registered @feature_flags_poller&.shutdown_poller flushed = if @sync_mode diff --git a/spec/posthog/mcp/client_spec.rb b/spec/posthog/mcp/client_spec.rb index a371e3d..8f68f78 100644 --- a/spec/posthog/mcp/client_spec.rb +++ b/spec/posthog/mcp/client_spec.rb @@ -3,6 +3,45 @@ require_relative 'spec_helper' RSpec.describe PostHog::MCP::Client do + describe 'lifecycle' do + [PostHog::Client, described_class].each do |client_class| + [{ test_mode: true }, { disable_singleton_warning: true }].each do |options| + it "preserves live-client warnings after #{client_class} shuts down with #{options.inspect}" do + clients = [] + logger = PostHog::Logging.logger + allow(logger).to receive(:warn) + live = PostHog::Client.new(api_key: 'phc_shared_lifecycle') + clients << live + unregistered = client_class.new({ api_key: 'phc_shared_lifecycle' }.merge(options)) + clients << unregistered + + expect(unregistered.shutdown(timeout: 2)).to be(true) + expect(unregistered.shutdown(timeout: 2)).to be(true) + clients << PostHog::Client.new(api_key: 'phc_shared_lifecycle') + expect(logger).to have_received(:warn).with(a_string_including('Multiple PostHog client instances')).once + + clients.each { |instance| instance.shutdown(timeout: 2) } + clients << PostHog::Client.new(api_key: 'phc_shared_lifecycle') + expect(logger).to have_received(:warn).with(a_string_including('Multiple PostHog client instances')).once + ensure + clients.each { |instance| instance.shutdown(timeout: 2) } + end + end + end + + [{ test_mode: true }, { sync_mode: true }, {}].each do |options| + it "constructs and shuts down with #{options.inspect}" do + stub_request(:post, 'https://us.i.posthog.com/batch/').to_return(status: 200, body: '{}') + mcp_client = described_class.new({ api_key: 'phc_lifecycle' }.merge(options)) + mcp_client.capture_tool_call('echo', distinct_id: 'user') + + expect(mcp_client.shutdown(timeout: 2)).to be(true) + expect(mcp_client.shutdown(timeout: 2)).to be(true) + expect(mcp_client.capture(event: 'after shutdown', distinct_id: 'user')).to be(false) + end + end + end + let(:client) { described_class.new(api_key: 'phc_test', test_mode: true) } it 'captures tool calls with $lib override, anonymous distinct id and error scalars' do