From c1430c363c2fbec118fed75ebb26c723dcba913f Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 20 Apr 2026 15:08:56 +0200 Subject: [PATCH 1/3] fix: trim whitespace from API keys and host config --- lib/posthog/client.rb | 21 ++++++++++++++++++++- spec/posthog/client_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/posthog/client.rb b/lib/posthog/client.rb index 74e4dab..b8068e5 100644 --- a/lib/posthog/client.rb +++ b/lib/posthog/client.rb @@ -74,7 +74,9 @@ def _decrement_instance_count(api_key) def initialize(opts = {}) symbolize_keys!(opts) - opts[:host] ||= 'https://app.posthog.com' + opts[:api_key] = normalize_string_option(opts[:api_key]) + opts[:personal_api_key] = normalize_string_option(opts[:personal_api_key], blank_as_nil: true) + opts[:host] = normalize_host_option(opts[:host]) @queue = Queue.new @api_key = opts[:api_key] @@ -102,6 +104,7 @@ def initialize(opts = {}) @personal_api_key = opts[:personal_api_key] check_api_key! + logger.error('api_key is empty after trimming whitespace; check your project API key') if @api_key == '' # Warn when multiple clients are created with the same API key (can cause dropped events) unless opts[:test_mode] || opts[:disable_singleton_warning] @@ -585,6 +588,22 @@ def check_api_key! raise ArgumentError, 'API key must be initialized' if @api_key.nil? end + def normalize_string_option(value, blank_as_nil: false) + return value unless value.is_a?(String) + + normalized = value.strip + return nil if blank_as_nil && normalized.empty? + + normalized + end + + def normalize_host_option(host) + normalized = normalize_string_option(host) + return 'https://app.posthog.com' if normalized.nil? || normalized.empty? + + normalized + end + def ensure_worker_running return if worker_running? diff --git a/spec/posthog/client_spec.rb b/spec/posthog/client_spec.rb index 28ce781..9ca14e6 100644 --- a/spec/posthog/client_spec.rb +++ b/spec/posthog/client_spec.rb @@ -16,6 +16,7 @@ module PostHog allow(logger).to receive(:warn) allow(logger).to receive(:info) allow(logger).to receive(:debug) + allow(logger).to receive(:error) end describe '#initialize' do @@ -37,6 +38,31 @@ module PostHog expect { Client.new api_key: API_KEY, skip_ssl_verification: true }.to_not raise_error end + it 'trims whitespace-sensitive options' do + client = Client.new( + api_key: " \n#{API_KEY}\t ", + personal_api_key: " \n\t ", + host: " \nhttps://eu.i.posthog.com/\t ", + test_mode: true + ) + + expect(client.instance_variable_get(:@api_key)).to eq(API_KEY) + expect(client.instance_variable_get(:@personal_api_key)).to be_nil + expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://eu.i.posthog.com/') + end + + it 'defaults a blank host after trimming whitespace' do + client = Client.new(api_key: API_KEY, host: " \n\t ", test_mode: true) + + expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://app.posthog.com') + end + + it 'logs when the api_key is empty after trimming whitespace' do + Client.new(api_key: " \n\t ", test_mode: true) + + expect(logger).to have_received(:error).with(include('api_key is empty after trimming whitespace')) + end + context 'singleton warning' do before do # Stub HTTP to allow creating clients without test_mode (which triggers the warning) From f1173797e098eb92f6b96afb26f4a7f3685a73db Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 20 Apr 2026 17:23:35 +0200 Subject: [PATCH 2/3] fix: default host to https://us.i.posthog.com Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/posthog/client.rb | 4 +- lib/posthog/defaults.rb | 2 +- spec/posthog/client_spec.rb | 38 ++++---- spec/posthog/feature_flag_error_spec.rb | 4 +- spec/posthog/feature_flag_spec.rb | 104 ++++++++++----------- spec/posthog/flag_definition_cache_spec.rb | 2 +- spec/posthog/flags_spec.rb | 26 +++--- 7 files changed, 90 insertions(+), 90 deletions(-) diff --git a/lib/posthog/client.rb b/lib/posthog/client.rb index b8068e5..2f8cd9f 100644 --- a/lib/posthog/client.rb +++ b/lib/posthog/client.rb @@ -59,7 +59,7 @@ def _decrement_instance_count(api_key) # on the calling thread. Useful in forking environments like Sidekiq # and Resque. Defaults to +false+. # @option opts [Proc] :on_error Handles error calls from the API. - # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://app.posthog.com` + # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com` # @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes. # Measured in seconds, defaults to 30. # @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation. @@ -599,7 +599,7 @@ def normalize_string_option(value, blank_as_nil: false) def normalize_host_option(host) normalized = normalize_string_option(host) - return 'https://app.posthog.com' if normalized.nil? || normalized.empty? + return 'https://us.i.posthog.com' if normalized.nil? || normalized.empty? normalized end diff --git a/lib/posthog/defaults.rb b/lib/posthog/defaults.rb index 1d918d7..2fdcef2 100644 --- a/lib/posthog/defaults.rb +++ b/lib/posthog/defaults.rb @@ -5,7 +5,7 @@ module Defaults MAX_HASH_SIZE = 50_000 module Request - HOST = 'app.posthog.com' + HOST = 'us.i.posthog.com' PORT = 443 PATH = '/batch/' SSL = true diff --git a/spec/posthog/client_spec.rb b/spec/posthog/client_spec.rb index 9ca14e6..a0c630b 100644 --- a/spec/posthog/client_spec.rb +++ b/spec/posthog/client_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module PostHog - flags_endpoint = 'https://app.posthog.com/flags/?v=2' + flags_endpoint = 'https://us.i.posthog.com/flags/?v=2' RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil @@ -33,7 +33,7 @@ module PostHog end it 'handles skip_ssl_verification' do - expect(PostHog::Transport).to receive(:new).with({ api_host: 'https://app.posthog.com', + expect(PostHog::Transport).to receive(:new).with({ api_host: 'https://us.i.posthog.com', skip_ssl_verification: true }) expect { Client.new api_key: API_KEY, skip_ssl_verification: true }.to_not raise_error end @@ -54,7 +54,7 @@ module PostHog it 'defaults a blank host after trimming whitespace' do client = Client.new(api_key: API_KEY, host: " \n\t ", test_mode: true) - expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://app.posthog.com') + expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://us.i.posthog.com') end it 'logs when the api_key is empty after trimming whitespace' do @@ -66,8 +66,8 @@ module PostHog context 'singleton warning' do before do # Stub HTTP to allow creating clients without test_mode (which triggers the warning) - stub_request(:post, 'https://app.posthog.com/batch/').to_return(status: 200, body: '{}') - stub_request(:get, %r{https://app\.posthog\.com/api/feature_flag/}).to_return(status: 200, body: '{}') + stub_request(:post, 'https://us.i.posthog.com/batch/').to_return(status: 200, body: '{}') + stub_request(:get, %r{https://us\.i\.posthog\.com/api/feature_flag/}).to_return(status: 200, body: '{}') end it 'warns when multiple clients are created with the same API key' do @@ -278,7 +278,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -323,7 +323,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -348,7 +348,7 @@ module PostHog 'off-feature' => false } } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: {}.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -373,7 +373,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 401, body: { 'error' => 'not authorized' }.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -390,7 +390,7 @@ module PostHog expect(properties['$feature/beta-feature']).to eq('random-variant') expect(properties['$active_feature_flags']).to eq(['beta-feature']) - assert_not_requested :get, 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + assert_not_requested :get, 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' end it 'manages memory well when sending feature flags' do @@ -414,7 +414,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_const('PostHog::Defaults::MAX_HASH_SIZE', 10) @@ -482,7 +482,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_const('PostHog::Defaults::MAX_HASH_SIZE', 10) @@ -639,7 +639,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -677,7 +677,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: flags_response.to_json) @@ -703,7 +703,7 @@ module PostHog it 'ignores feature flags with invalid send_feature_flags parameter' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { flags: [] }.to_json) c = Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) @@ -741,7 +741,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # This should NOT be called because only_evaluate_locally is true @@ -793,7 +793,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # This should NOT be called because only_evaluate_locally is true @@ -844,7 +844,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # This SHOULD be called because only_evaluate_locally is explicitly false @@ -1186,7 +1186,7 @@ def run # Mock response for api/feature_flag stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # Mock response for `/flags` diff --git a/spec/posthog/feature_flag_error_spec.rb b/spec/posthog/feature_flag_error_spec.rb index 716cf74..9a20385 100644 --- a/spec/posthog/feature_flag_error_spec.rb +++ b/spec/posthog/feature_flag_error_spec.rb @@ -4,8 +4,8 @@ module PostHog describe 'Feature Flag Error Tracking' do - let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' } - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) } before do diff --git a/spec/posthog/feature_flag_spec.rb b/spec/posthog/feature_flag_spec.rb index 1f72000..9f5a4c3 100644 --- a/spec/posthog/feature_flag_spec.rb +++ b/spec/posthog/feature_flag_spec.rb @@ -6,7 +6,7 @@ module PostHog RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil - flags_endpoint = 'https://app.posthog.com/flags/?v=2' + flags_endpoint = 'https://us.i.posthog.com/flags/?v=2' describe 'local evaluation' do it 'evaluates person properties' do @@ -38,7 +38,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # shouldn't call flags @@ -89,7 +89,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # shouldn't call flags @@ -177,7 +177,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -252,7 +252,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -373,7 +373,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -435,7 +435,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -495,7 +495,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -554,7 +554,7 @@ module PostHog # We don't go to `/flags` if local eval is enabled and the flag is not in list of all flag definitions stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -573,7 +573,7 @@ module PostHog # TRICKY: Pretty hard to simulate a timeout using sleep with WebMock, so we'll just raise an error stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_raise(Net::ReadTimeout) stub_request(:post, flags_endpoint) @@ -620,7 +620,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -686,7 +686,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -758,7 +758,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -780,7 +780,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -832,7 +832,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -883,7 +883,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -933,7 +933,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res_updated.to_json) # force reload to simulate poll interval @@ -978,7 +978,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -1037,7 +1037,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -1088,7 +1088,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -1150,7 +1150,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -2039,7 +2039,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # shouldn't call `/flags` @@ -3086,7 +3086,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # shouldn't call `/flags` @@ -4174,7 +4174,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => flag_res }.to_json) stub_request(:post, flags_endpoint) @@ -4193,7 +4193,7 @@ module PostHog it 'get all flags and payloads with fallback and empty local flags' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [] }.to_json) stub_request(:post, flags_endpoint) @@ -4270,7 +4270,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => flag_res }.to_json) stub_request(:post, flags_endpoint) @@ -4326,7 +4326,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [basic_flag, disabled_flag] }.to_json) stub_request(:post, flags_endpoint) @@ -4366,7 +4366,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [basic_flag_res] }.to_json) stub_request(:post, flags_endpoint) @@ -4387,7 +4387,7 @@ module PostHog it 'evaluates boolean feature flags with `/flags`' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [] }.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: { 'featureFlags' => {}, @@ -4436,7 +4436,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { flags: [multivariate_flag] }.to_json) stub_request(:post, flags_endpoint) .to_return(status: 200, body: { featureFlagPayloads: { 'first-variant' => { b: 'json' } } }.to_json) @@ -4468,11 +4468,11 @@ module PostHog mock_decrypted_payload = '"super secret payload in plaintext"' stub_request( :get, - "https://app.posthog.com/api/projects/@current/feature_flags/#{encrypted_payload_flag_key}/remote_config?token=testsecret" + "https://us.i.posthog.com/api/projects/@current/feature_flags/#{encrypted_payload_flag_key}/remote_config?token=testsecret" ).to_return(status: 200, body: mock_decrypted_payload) stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { flags: [] }.to_json) c = Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) @@ -4506,7 +4506,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => flag_res }.to_json) stub_request(:post, flags_endpoint) @@ -4556,7 +4556,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint) @@ -4568,7 +4568,7 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 400, body: { 'error' => 'went_wrong!' }.to_json) # force reload to simulate poll interval @@ -4609,17 +4609,17 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # Add the exact stub for the `/flags` endpoint as recommended in the error - stub_request(:post, 'https://app.posthog.com/flags/?v=2').with( + stub_request(:post, 'https://us.i.posthog.com/flags/?v=2').with( body: '{"distinct_id":"distinct_id","groups":{},"person_properties":{"distinct_id":"distinct_id","region":"USA"},"group_properties":{},"token":"testsecret"}', # rubocop:disable Layout/LineLength headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type' => 'application/json', - 'Host' => 'app.posthog.com', + 'Host' => 'us.i.posthog.com', 'User-Agent' => "posthog-ruby#{PostHog::VERSION}" } ).to_return(status: 200, body: '{"featureFlags": {}}', headers: {}) @@ -4632,7 +4632,7 @@ module PostHog # Now simulate quota limit with 402 response stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 402, body: { error: 'quota_limit_exceeded' }.to_json) # Force reload to simulate poll interval @@ -4692,11 +4692,11 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # Mock API response - user is in the static cohort - stub_request(:post, 'https://app.posthog.com/flags/?v=2') + stub_request(:post, 'https://us.i.posthog.com/flags/?v=2') .to_return(status: 200, body: { 'featureFlags' => { 'multi-condition-flag' => 'set-1' } }.to_json) @@ -4749,11 +4749,11 @@ module PostHog stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) # Mock API response - user is in the static cohort - stub_request(:post, 'https://app.posthog.com/flags/?v=2') + stub_request(:post, 'https://us.i.posthog.com/flags/?v=2') .to_return(status: 200, body: { 'featureFlags' => { 'multi-condition-flag' => 'variant-1' }, 'featureFlagPayloads' => { 'multi-condition-flag' => '{"message": "from-api"}' } @@ -4773,7 +4773,7 @@ module PostHog end describe 'get_feature_flag_result' do - let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' } + let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' } it 'returns a FeatureFlagResult with flag value and payload' do api_feature_flag_res = { @@ -4792,7 +4792,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -4827,7 +4827,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [multivariate_flag] }.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -4861,7 +4861,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -4895,7 +4895,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -4926,7 +4926,7 @@ module PostHog } stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: api_feature_flag_res.to_json) stub_request(:post, flags_endpoint).to_return(status: 400) @@ -4949,7 +4949,7 @@ module PostHog it 'returns nil when flag evaluation returns nil' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [] }.to_json) stub_request(:post, flags_endpoint) @@ -4965,7 +4965,7 @@ module PostHog it 'falls back to remote evaluation when needed' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [] }.to_json) stub_request(:post, flags_endpoint) @@ -4994,7 +4994,7 @@ module PostHog it 'includes request_id and evaluated_at from remote evaluation in event' do stub_request( :get, - 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' + 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' ).to_return(status: 200, body: { 'flags' => [] }.to_json) stub_request(:post, flags_endpoint) diff --git a/spec/posthog/flag_definition_cache_spec.rb b/spec/posthog/flag_definition_cache_spec.rb index 2aba7d1..511c381 100644 --- a/spec/posthog/flag_definition_cache_spec.rb +++ b/spec/posthog/flag_definition_cache_spec.rb @@ -85,7 +85,7 @@ def provider.shutdown; end describe 'flag definition cache integration' do let(:provider) { MockCacheProvider.new } - let(:local_eval_url) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:local_eval_url) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } # Sample flag data with string keys (simulating JSON deserialization from cache) let(:sample_flags_data) do diff --git a/spec/posthog/flags_spec.rb b/spec/posthog/flags_spec.rb index b7771ec..41b9b40 100644 --- a/spec/posthog/flags_spec.rb +++ b/spec/posthog/flags_spec.rb @@ -5,8 +5,8 @@ module PostHog describe 'FeatureFlagsPoller#get_flags' do - let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' } - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) } let(:poller) { client.instance_variable_get(:@feature_flags_poller) } let(:flags_v3_response) do @@ -23,7 +23,7 @@ module PostHog 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer testsecret', - 'Host' => 'app.posthog.com', + 'Host' => 'us.i.posthog.com', 'User-Agent' => "posthog-ruby#{PostHog::VERSION}" } ) @@ -334,7 +334,7 @@ module PostHog describe 'Client#get_feature_flag' do let(:client) { Client.new(api_key: API_KEY, personal_api_key: nil, test_mode: true) } - let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' } + let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' } let(:flags_v4_response) do JSON.parse(File.read(File.join(__dir__, 'fixtures', 'test-flags-v4.json')), symbolize_names: true) end @@ -365,8 +365,8 @@ module PostHog end describe 'FeatureFlagsPoller#get_remote_config_payload' do - let(:remote_config_endpoint) { 'https://app.posthog.com/api/projects/@current/feature_flags/test-flag/remote_config?token=testsecret' } - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:remote_config_endpoint) { 'https://us.i.posthog.com/api/projects/@current/feature_flags/test-flag/remote_config?token=testsecret' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: 'testsecret', personal_api_key: 'personal_key', test_mode: true) } let(:poller) { client.instance_variable_get(:@feature_flags_poller) } @@ -387,7 +387,7 @@ module PostHog 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type' => 'application/json', 'Authorization' => 'Bearer personal_key', - 'Host' => 'app.posthog.com', + 'Host' => 'us.i.posthog.com', 'User-Agent' => "posthog-ruby#{PostHog::VERSION}" } ) @@ -680,7 +680,7 @@ module PostHog end describe 'integration with feature flags' do - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: 'testsecret', personal_api_key: 'personal_key', test_mode: true) } let(:poller) { client.instance_variable_get(:@feature_flags_poller) } @@ -781,8 +781,8 @@ module PostHog end describe 'Flag dependencies' do - let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' } - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) } let(:poller) { client.instance_variable_get(:@feature_flags_poller) } @@ -794,7 +794,7 @@ module PostHog 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer testsecret', - 'Host' => 'app.posthog.com', + 'Host' => 'us.i.posthog.com', 'User-Agent' => "posthog-ruby#{PostHog::VERSION}" } ) @@ -1529,7 +1529,7 @@ def stub_feature_flags(flags) before do # Stub the initial feature flag definitions request - stub_request(:get, 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true') + stub_request(:get, 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true') .to_return(status: 200, body: { flags: [] }.to_json) end @@ -1621,7 +1621,7 @@ def stub_feature_flags(flags) end describe 'FeatureFlagsPoller ETag support' do - let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } + let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' } let(:client) { Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) } let(:poller) { client.instance_variable_get(:@feature_flags_poller) } From c543d07b75be5955032cf3d585fb7f5afd86190f Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 20 Apr 2026 17:25:07 +0200 Subject: [PATCH 3/3] chore: add release changeset Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/trim-whitespace-default-host.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/trim-whitespace-default-host.md diff --git a/.changeset/trim-whitespace-default-host.md b/.changeset/trim-whitespace-default-host.md new file mode 100644 index 0000000..42166a9 --- /dev/null +++ b/.changeset/trim-whitespace-default-host.md @@ -0,0 +1,5 @@ +--- +'posthog-ruby': patch +--- + +Trim whitespace from `api_key`, `personal_api_key`, and `host` config values, and default `host` to `https://us.i.posthog.com`.