Skip to content

feat(server): expose flags that local evaluation could not resolve - #896

Open
gustavohstrassburger wants to merge 2 commits into
mainfrom
posthog/unresolved-local-flags
Open

gustavohstrassburger wants to merge 2 commits into
mainfrom
posthog/unresolved-local-flags

Conversation

@gustavohstrassburger

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

Why: sdk-specs #85 defines how a server SDK reports flags that local evaluation could not resolve. Today such a flag is absent from the evaluateFlags() snapshot, so it looks the same as a flag that does not exist, and $feature_flag_called reports flag_missing for both. With onlyEvaluateLocally this is permanent. posthog-node already implements this (PostHog/posthog-js#5234).

This replaces #827, which was closed as stale. It is rebased on main and adds the spec parts that #827 did not have.

Changes in posthog-server:

  • PostHogFeatureFlagEvaluations.unresolvedFlags maps each unresolved flag to a PostHogUnresolvedFlagReason (EXPERIENCE_CONTINUITY, UNSUPPORTED_DEFINITION, UNRESOLVED_DEPENDENCY, MISSING_CONTEXT). When several causes apply, the spec's fixed precedence picks one. Snapshots from only() / onlyAccessed() have no entries, and reading the map is not an access.
  • A snapshot read of an unresolved flag reports local_evaluation_inconclusive instead of flag_missing. Values, keys and capture enrichment do not change.
  • Every inconclusive raise site carries a reason, including the newer regex and numeric sites. A bad person value is MISSING_CONTEXT; a bad filter value is UNSUPPORTED_DEFINITION.
  • Behavior change: a condition no longer stops at the first inconclusive filter. If another filter does not match, the condition resolves locally as no match instead of falling back to /flags.
  • When definitions load, the SDK logs one warning (also with debug off) for each flag local evaluation can never resolve. It repeats only when that flag's definition changes; 304 and unchanged cache loads do not repeat it.

Divergences from the spec or posthog-node:

  • A missing group key still resolves to false, as before, so it is not reported as MISSING_CONTEXT. posthog-node does the same.
  • The SDK has no device-id bucketing, so the device-id acceptance row does not apply.
  • A static cohort is an ordinary inconclusive filter (UNSUPPORTED_DEFINITION). posthog-node also forces a server fallback when the static cohort is the first filter it cannot check; this SDK has no separate "requires server" signal, so it keeps its current behavior.
  • An unexpected error inside a dependency stays flag_missing instead of UNRESOLVED_DEPENDENCY.

💚 How did you test it?

Unit tests mirror every scenario tagged @unresolved_flags_capable and @unresolvable_flag_warning_capable in acceptance/public/evaluate-flags.feature, plus precedence, the condition loop and the load-time checks. Run locally with JDK 17:

  • ./gradlew :posthog-server:test (656 tests, 0 failures)
  • make checkFormat
  • ./gradlew :posthog-server:apiCheck
  • ./gradlew :posthog-server-openfeature:test

No manual testing in an app.

📝 Checklist

  • All commits are signed (see Commit signing)
  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)


Created with PostHog Desktop

🤖 Generated with Claude Code

Implements the "Snapshot unresolved flag reporting" and "Warning for flags local evaluation never resolves" requirements of the evaluate-flags spec (PostHog/sdk-specs#85).

- Add `unresolvedFlags` to the `evaluateFlags()` snapshot, with a `PostHogUnresolvedFlagReason` for each flag and the spec's fixed precedence.
- Report `local_evaluation_inconclusive` instead of `flag_missing` when a snapshot read hits an unresolved flag.
- Do not stop a condition at the first inconclusive filter, so a filter that does not match resolves the condition locally.
- Log one warning per flag that local evaluation can never resolve, repeated only when its definition changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

Generated-By: PostHog Desktop
Task-Id: 39793b31-809e-4689-a5d8-b4359cbb22c1
@gustavohstrassburger gustavohstrassburger self-assigned this Oct 27, 2026

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed d8d13e9: 3 Should fix. This pull request is large, so the review ran in 4 parts with less depth than usual.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

Comment thread posthog-server/src/main/java/com/posthog/server/internal/FlagEvaluator.kt Outdated
@gustavohstrassburger
gustavohstrassburger marked this pull request as ready for review October 28, 2026 16:18
@gustavohstrassburger
gustavohstrassburger requested a review from a team as a code owner October 28, 2026 16:18

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PostHog Review · Standard · reviewed 987b468: no findings. This pull request is large, so the review ran in 4 parts with less depth than usual.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant