66 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 8ee8950230 Go 1.27.1, and the code it can say more simply
The module declares go 1.27.1 and the adapter image follows. go fix
applied the modernizers and left ptrString and ptrBool with no callers,
so they are gone; omitzero is not applied, since it changes what the
JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:52:31 -07:00
zeekay 42ac61f78d merge: upstream/main (preserve Hanzo brand, 55 commits) 2026-06-02 09:56:19 -07:00
Manoel Aranda NetoandGitHub 920035f38a docs: document public APIs and hide testing-only flag definitions method (#208)
* docs: add public API Go doc comments

* refactor: hide client feature flag definitions API
2026-05-27 17:48:42 +02:00
Dylan MartinandGitHub 41b8261a8e fix: reject leading-zero semver values in local evaluation (#200) 2026-05-21 08:28:59 -07:00
PatricioandGitHub 082686afd3 feat(flags): support mixed targeting in local evaluation (#192)
* feat(flags): support mixed targeting in local evaluation

* fix(flags): include distinct_id in person_properties for /flags request
2026-05-05 09:14:54 -03:00
Dylan MartinandGitHub a3293aaa2f feat: add EvaluateFlags() API for single-call flag evaluation (#191)
Adds a Phase 1 implementation of the Server SDK Feature Flag
Evaluations RFC, mirroring posthog-js#3476 and posthog-python#539.

Client.EvaluateFlags returns a FeatureFlagEvaluations snapshot built
from at most one /flags request. The snapshot powers IsEnabled,
GetFlag, and GetFlagPayload checks, fires deduped $feature_flag_called
events with full v4 metadata (id, version, reason, request_id), and can
be attached to a Capture event via the new Capture.Flags field to
populate $feature/<key> and $active_feature_flags with no extra
network call.

The dedup logic for $feature_flag_called is extracted into
captureFlagCalledIfNeeded so the existing single-flag path and the new
snapshot path share the same per-distinct_id LRU cache and behave
identically.

OnlyAccessed and Only return filtered child snapshots with independent
access tracking, so filtering for a Capture does not back-propagate to
the parent. A new Config.FeatureFlagsLogWarnings option silences their
warnings for callers that prefer quieter helpers.

Capture.SendFeatureFlags is unchanged and not deprecated; Phase 2 will
follow up with deprecations and migration guidance.

Task-Id: b9d98122-fe61-462a-bfb3-6e1be3a8966a
2026-05-01 13:03:26 -07:00
PatricioandGitHub e27db828f7 feat(flags): switch local evaluation endpoint to /flags/definitions (#182) 2026-04-20 09:05:08 +02:00
Luke BeltonandGitHub 96566005fd feat: add locally_evaluated property to $feature_flag_called (#176)
* add `locally_evaluated` property to `$feature_flag_called` events
2026-04-14 14:52:46 +01:00
Dustin ByrneandGitHub 139d59d59a fix: dramatically reduce memory allocations in feature flag evaluation (#172)
Addresses high RAM usage and OOMs when using feature flags at scale.
Tested with 853 flags + 12 cohorts (production-like setup):

  Before: 17,463 allocs/op, 201 MB/op, 51 ms/op
  After:    139 allocs/op,  56 KB/op, 129 µs/op

Key changes:
- Cache flagsByKey in flagsState at load time (was O(N²) in GetAllFlags)
- sha1.Sum with stack buffer instead of sha1.New + hash.Write (zero heap escapes)
- Append '.' separator inside calculateHash buffer (avoid string concat alloc)
- Cache getSystemContext/getVersion with sync.Once
- Pre-compute at load time: variant lookup tables, decoded payloads, parsed cohort values
- Cache compiled regexps via sync.Map
- Eliminate double flag evaluation (single-pass GetFeatureFlagWithPayload)
- Lazy evaluationCache only for flags with dependencies
- Sentinel errors + direct type assertions replacing errors.As
- valueToString replacing fmt.Sprintf throughout matchProperty
- Defer empty map allocation in validate() to remote path only
- Pre-merge distinct_id once before GetAllFlags loop
- Embed payload/variant strings in FeatureFlagResult struct
- Skip Properties merge for flags without person property conditions
2026-03-26 09:55:28 +01:00
Dylan MartinandGitHub 6c3b47edc0 feat(flags): add semver targeting to local evaluation (#166)
* add semver targeting to local evaluation

* chore: bump version to 1.11.0 [version bump]

* address PR feedback from Copilot

- Change "person property value" to "property value" in error messages
  since matchProperty is used for both person and group properties
- Validate core version format before stripping pre-release/build suffixes
  to reject malformed inputs like "1-2.3" or "-alpha"
- Return error when a version component is present but empty (e.g., "1..3"
  or "1.") instead of silently defaulting to 0
- Add tests for newly rejected malformed inputs

* fix: run go fmt
2026-03-04 15:23:25 -08:00
zeekay b4e1366340 rebrand: export Insights types instead of PostHog
Rename Go package from posthog to insights across all 80+ files.
Consumers now import as `insights "github.com/hanzoai/insights-go"`
and use insights.Client, insights.Config, insights.Capture, etc.

- Rename posthog.go -> insights.go, posthog_test.go -> insights_test.go
- Update package declaration from posthog to insights in all .go files
- Update SDKName from posthog-go to insights-go
- Update DefaultEndpoint from app.posthog.com to insights.hanzo.ai
- Update all FieldError/ConfigError type strings
- Update User-Agent headers, log prefixes, comments, and docs
- Update JSON test fixtures to match new SDK name
- Update CLI, examples, extras, and sdk_compliance_adapter
- Fix pre-existing go vet warnings (non-constant format strings)
2026-03-13 19:54:58 -07:00
AndersandGitHub 227dc9bb39 feat: Add device_id parameter to /flags requests (#157)
Add support for passing device_id to the PostHog /flags endpoint.
This allows clients to identify devices separately from users when
evaluating feature flags.

- Add DeviceId field to FlagsRequestData (omitted when nil)
- Add DeviceId to FeatureFlagPayload and FeatureFlagPayloadNoKey
- Thread device_id through all flag evaluation paths
- Add tests verifying device_id is passed in request payload
2026-02-04 14:30:37 +01:00
Dustin ByrneandGitHub f93ef34367 fix: Flag payloads can contain JSON types (#161)
Previously we expected serialized JSON strings. However, the API can
also return any valid JSON type
2026-01-30 12:44:01 -05:00
Dustin ByrneandGitHub dfac8b7fdb feat: Add GetFeatureFlagResult method to client (#159)
* refactor: Rename `FeatureFlagResult` -> `featureFlagEvaluationResult`

It's an internal type used to track request metadata to be reported via
$feature_flag_called. This shouldn't be conflated with the
`getFeatureFlagResult()` API.

* feat: Add `GetFeatureFlagResult` method to client

This method returns an object indicating if the flag is enabled, what
the variant is, and a method for parsing the payload as a given type. It
emits a 'feature flag called' event by default, and will become
the canonical method of fetching a feature flag

* fix: Always return nil if a flag doesn't exist

* doc: Update CHANGELOG
2026-01-28 17:30:01 +00:00
Eli ReismanandGitHub 8a6e9ae51e feat: refactor internal event submission pipeline and test suites (#151)
* intitial refactor:

- Add Context-based graceful shutdown and drain phase for enqueued requests
- Use buffered channels vs. mutex for concurrency mgmt.
- Use atomic.Pointer vs. locks for FF mgmt
- Use Callback ifc vs. lossy internal tracking for unit test result validation
- Add new APIs mirroring current that accept caller's Context to control
  operation timeouts

* expose batch submission timeout with default equiv to old hardcoded timeout

* restructure event submission and batching pipeline

* overhaul tests; implement new stress tests and address coverage gaps

* parallelize tests where safe to do so (3x CI time savings!)

* introduce faster pure Go JSON seralization library

* more refactors: unit tests & benchmarks

* DRY up shutdown req drain and prepareForSend serializer

* use go-json in CLI utils too; remove redundant helpers

* move error type to proper pkg

* DRY up loop() and fix deadlock in old test case

* normalize CI and Makefile test and benchmark target behavviors

* more realistic feature flags benchmark

* replace leaky time.After pattern with idiomatic safer Timers

* internal queue size minimums decoupled from batch and worker configs

* remove dead comments

* fix test bug

* update default http.Transport cfg to avoid conn pool as bottleneck; update batch enqueue limiting strategy and tests/benches

* adjust stress test defaults to match prod

* update flaky tests and rm stale file

* make graceful shutdown timeout opt-in for now

* fix tests that reverted to hold helper utils during rebase

* fix flaky test due to overhead from -race detector now default in test flags

* add more graceful shutdown test cases
2026-01-13 12:22:44 -08:00
Phil HaackandGitHub 11fb61b6cf feat(flags): Add ETag support for local evaluation polling (#139)
* Add ETag support for local evaluation polling

Use conditional requests with If-None-Match header to reduce bandwidth
when polling for feature flag definitions. The server returns 304 Not
Modified when flags haven't changed, avoiding unnecessary data transfer.

- Store ETag from server responses
- Send If-None-Match header on subsequent requests
- Handle 304 Not Modified by keeping cached flags
- Clear ETag on quota limit (402) or when server stops sending it

* Fix response body leak on 304/402 responses

Move defer res.Body.Close() immediately after the error check to ensure
the response body is closed for all status codes, not just 200 OK.

Previously, early returns for 304 Not Modified and 402 Payment Required
would skip the defer, causing connection pool leaks over time.

* Replace time.Sleep with deterministic synchronization in tests

- Use GetFeatureFlags() which blocks until initial fetch completes
- Use waitForCondition() helper with polling for ForceReload operations
- Use atomic.Value instead of mutex for thread-safe string storage
- Tests are now faster (0.06s vs 1.22s) and more reliable

* Add test for 304 response without ETag header

Verify that the stored ETag is preserved when the server returns a 304
Not Modified response without including an ETag header. This is an edge
case but ensures defensive behavior.

* Update comment per code review

* Add ETag polling test example

Adds an interactive example to demonstrate ETag support for local
evaluation polling. The example polls every 5 seconds and logs
the If-None-Match header sent and the response status (200 vs 304)
to show when cached flags are used.

* Test FeatureFlagsPoller directly

Per review feedback, this makes the tests simpler.
2025-12-04 11:00:30 -08:00
Dustin ByrneandGitHub 36b82c1299 feat: Add local evaluation support of datetime operators (#131)
feat: Local eval support for date types
2025-11-21 16:58:20 -05:00
Phil HaackandGitHub 49f7945205 fix(flags): static cohort evaluation in multi-condition flags (#129)
* Fix static cohort evaluation in multi-condition flags

When a feature flag has multiple conditions and one contains a static
cohort (not available locally), the SDK now correctly falls back to the
API for the entire flag evaluation instead of skipping the cohort
condition and evaluating the next condition locally.

Previously, if condition 1 had a static cohort and condition 2 matched
locally, the SDK would incorrectly return the variant from condition 2,
even though the user might actually be in the static cohort.

Changes:
- Add RequiresServerEvaluationError type to distinguish missing server
  data from evaluation errors
- Update matchCohort() to return RequiresServerEvaluationError when
  cohort not found locally
- Update error handling in matchPropertyGroup() to immediately propagate
  RequiresServerEvaluationError
- Update matchFeatureFlagProperties() to trigger API fallback on
  RequiresServerEvaluationError
- Add test case verifying correct API fallback behavior

This ensures the correct variant is returned by consulting the server
when static cohorts are involved.

Follows same pattern as Node.js SDK fix: PostHog/posthog-js#2465

* Add test for GetFeatureFlagPayload with static cohorts

Adds TestGetFeatureFlagPayloadFallbackToAPIWhenFlagHasStaticCohort
to verify that GetFeatureFlagPayload correctly falls back to the API
when a flag contains a static cohort.

This test ensures the fix in featureflags.go:418-436 works correctly
for GetFeatureFlagPayload, matching the behavior of GetFeatureFlag.
When local evaluation fails due to a missing cohort (indicating a
static cohort), the SDK should fall back to the API and return the
API payload rather than failing or returning incorrect data.
2025-10-21 13:41:05 -07:00
Andy ZhaoandGitHub b7a9a168ad fix: don't sort condition sets with variant overrides to top (#127)
* fix: don't sort condition sets with variant overrides to top

* update version and changelog
2025-09-22 16:17:39 -04:00
Dylan MartinandGitHub c87892275e move logs to warn (#124) 2025-09-01 11:55:24 -07:00
Phil HaackandGitHub 3bc0ffc4d6 feat(flags): Implement local evaluation of flag dependencies (#121) 2025-08-25 14:22:08 -07:00
Dylan MartinandGitHub 5077d0581f feat(flags): make the SendFeatureFlags parameter more declarative and ergonomic (#115) 2025-07-23 17:11:12 -07:00
Phil HaackandGitHub 20f6777342 chore: Warn when new flag filter type is encountered. (#111) 2025-07-15 11:35:30 -07:00
Phil HaackandGitHub 26185430a0 fix: Ignore new flag filter type in local evaluation (#110) 2025-07-10 14:44:07 -07:00
Paweł SzczurandGitHub b14d84ea0b chore: move URL parsing errors to init (#109)
* Move URL parsing errors to init, validate endpoint when creating client
* Proper logger
* fix issues with timeout test
* do not waste go routines (fix bug with infinite goroutines / http requests)
* minor fixes to errors
2025-06-10 23:00:20 +02:00
Paweł SzczurandGitHub 115689e353 fix: race in accessing feature flags (#107) 2025-05-29 09:36:43 +02:00
Paweł SzczurandGitHub 5a4aa1046c fix: $group_key should be override in local eval (#105)
* Fixes the $group_key override
* Fix person properties override of distinct_id
2025-05-24 00:36:40 +02:00
7b79baa601 feat: add DisableGeoIP option (#104)
Co-authored-by: jackhopner <[email protected]>
2025-05-23 11:06:01 +02:00
Paweł SzczurandGitHub b8b61e6454 fix: make RolloutPercentage *float64 to match backend (#103)
Make RolloutPercentage *float64 to match backend
2025-05-21 17:57:12 +02:00
38432332b7 fix: the local evaluation was not using rollout percentage (#102)
* simplify the hash calculation
* Fixes the feature flag local eval
* fix the hash when the group name is used for computation
* bump version & add changelog item for 1.5.5

Co-authored-by: Phil Haack <[email protected]>
2025-05-21 17:08:31 +02:00
Dylan MartinandGitHub e54dcc0ba7 chore(flags): use new /flags endpoint instead of /decide (#96)
* use /flags instead of /decide

* update version

* renamed file
2025-04-30 20:43:36 -04:00
Phil HaackandGitHub adb3256ec8 refactor: Consolidate decide code (#89)
* Consolidate decide code

There were two implementations of decide. That's not the end of the world, but I'm about to implement a new decide format (`/decide?v=4`) and I don't want to have to do it in two places.

So this adds a new file `decide.go` with a new interface `decider` and an implementation `decideClient` that both `Client` and `FeatureFlagsPoller` uses.

* Remove redundant client

* Use `io.ReadAll`

`ioutil.ReadAll` is deprecated.

* Pass the already created decider into the poller

* Move decide types into `decide.go`

* Remove unused method
2025-04-01 10:03:52 -07:00
Phil HaackandGitHub 94155adcf1 chore: Remove isSimpleFlag related code (#88)
Remove isSimpleFlag related code

See https://github.com/PostHog/posthog/issues/30571 for more details.
2025-03-31 17:02:35 -07:00
Phil HaackandGitHub e024c47d21 fix: Do not convert string variant results into bool (#87)
The words "true" and "false" are valid variant keys. It's incorrect to convert them to `true` and `false`. This could result in a case where `isEnabled` returns `false` when in fact, it's `true` but the variant selected is named "false".

Added a test to confirm this change.
2025-03-31 14:33:26 -07:00
Dylan MartinandGitHub 80fa304de5 feat(feature-flags): support quota limiting for feature flags (#85)
* tests

* helper method

* bump version + changelog
2025-02-26 19:16:45 -05:00
HavenandGitHub 17135669bc feat(flags): Add method for fetching decrypted remote config flag payload (#84)
* feat(flags): Add method for fetching decrypted remote config flag payload

* fix tests

* fix broken test

* Address comment around user agent header

* tweak

* bump minor version

* Use flag key instead of id

* Add json examples

* address comment

* Clean up method name
2025-02-13 14:21:15 -08:00
João VitorandGitHub f024683219 add support for fetching feature flag payloads (#64)
* add support for fetching feature flag payloads

* improve mock server handler readability

* drying code

* update decide fixtures

* pr feedback
2024-08-13 17:50:23 -04:00
Martin KraftandGitHub 5cdbda3cae Better error for feature flags fetch without errors. Adds missing format string verbs to poller Errorf. (#48) 2024-08-08 13:49:57 -04:00
Dylan MartinandGitHub 2e0b9a246a feat(flags): fall back to /decide endpoint for GetFeatureFlag and GetAllFlags so that users can use this library without needing a personal API Key (#46)
* initial commit

* info about the personal api key

* adding docs

* logs and comments

* adding changelog
2024-08-07 19:43:26 -04:00
Roman ZaynetdinovandGitHub 72086fda55 Do not push to the loaded channel when flags' request fails (#38) 2024-03-27 11:25:32 +00:00
Neil KakkarandGitHub 58c49231af fix(flags): Add configurable flag timeouts (#37) 2024-03-15 13:09:56 +00:00
Roman ZaynetdinovandGitHub c6d31c5353 Allow to configure when next flags polling happens (#36)
We want to poll at the same time from all instances.
2024-03-14 15:57:07 +00:00
Roman ZaynetdinovandGitHub 2ada22a25b Support multiple cohorts in local evaluation (#34) 2024-02-21 13:58:34 +00:00
Roman ZaynetdinovandGitHub 05b60496fe Log flag name when failed to compute it locally (#32) 2024-02-08 11:15:51 +00:00
Neil KakkarandGitHub f3d7f91151 fix(flags): Don't match unknown operators (#24) 2024-01-10 10:58:35 +00:00
Tristan CartledgeandGitHub 44244ec407 fix: fixes a nil panic when unable to fetch feature flags (#20)
fix: fixes a nil panic when unable to fetch feature flags and err != nil which means res.Body will be nil at defer time because we aren't returning early enough from the method
2023-08-01 15:02:17 +01:00
35e4961c7b featureflags: propagate groups value to getFeatureFlagVariant (#16)
Co-authored-by: Advith Chelikani <[email protected]>
2022-12-21 11:52:52 +00:00
Neil KakkarandGitHub e5a01c6561 feat(flags): Add support for variant overrides (#12) 2022-11-22 14:50:26 +00:00
6d872f6582 Local Feature Flag Evaluation (#6)
* initial translation

* matching func

* consistency tests working

* working person test

* operator tests

* regex test

* handle groups

* getallflags func and more tests

* working tests

* working tests

* add onlyEvaluateLocally and sendFeatureFlagEvents

* consistency tests speedup

* more tets and stubs

* fix

* add map limit

* use struct payload

* fix payloads

* Update feature_flags_test.go

Co-authored-by: Neil Kakkar <[email protected]>

* test dict separately

* remove default result

* address comments

* condense

* reorg request functions

* make sure isfeatureenabled returns null

Co-authored-by: Neil Kakkar <[email protected]>
2022-08-16 20:03:21 -04:00
Alex Kim b80cb89603 address feedback 2022-07-06 12:19:14 -04:00