Skip to content

fix: Disable client when project token is missing - #189

Merged
marandaneto merged 8 commits into
mainfrom
fix/noop-empty-project-token
May 6, 2026
Merged

fix: Disable client when project token is missing#189
marandaneto merged 8 commits into
mainfrom
fix/noop-empty-project-token

Conversation

@marandaneto

@marandaneto marandaneto commented Apr 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Normalize ProjectToken and legacy ProjectApiKey by trimming and treating empty/whitespace as missing
  • Add a PostHogOptions.Disabled user-configurable flag (default false) and compute the effective disabled state in PostHogClient
  • No-op client calls when disabled instead of sending an empty api_key or throwing later
  • Log a warning when public client methods are called while the SDK is disabled
  • No-op public methods that require personal_api_key when it is not configured, including local-only feature flag evaluation and remote config payloads
  • Add focused coverage for missing/empty/whitespace ProjectToken and legacy ProjectApiKey, disabled-call logging, and personal_api_key-protected methods

Tests

  • dotnet test tests/UnitTests/UnitTests.csproj --framework net8.0 --filter "FullyQualifiedName~TheDisabledClient|FullyQualifiedName~ThePersonalApiKeyProtectedMethods|FullyQualifiedName~LogsWarningWhenPersonalApiKey|FullyQualifiedName~LogsErrorWhenProjectTokenIsMissing|FullyQualifiedName~LogsWarningWhenProjectApiKeyIsUsed"
  • dotnet test tests/UnitTests/UnitTests.csproj --framework net8.0 (fails in existing CaptureException tests: CaptureExceptionWithInvalidFilePathInStackFrame, CaptureExceptionCauseIOFailureEmptyContext)

Note: requested context files context.md and plan.md were not present in this checkout.

@github-actions

github-actions Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

posthog-dotnet Compliance Report

Date: 2026-05-06 09:19:40 UTC
Duration: 431ms

⚠️ Some Tests Failed

2/16 tests passed, 14 failed


Feature_Flags Tests

⚠️ 2/16 tests passed, 14 failed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 33ms
Request Payload.Flags Request Uses V2 Query Param 16ms
Request Payload.Flags Request Hits Flags Path Not Decide 4ms
Request Payload.Flags Request Omits Authorization Header 3ms
Request Payload.Token In Flags Body Matches Init 4ms
Request Payload.Groups Round Trip 4ms
Request Payload.Groups Default To Empty Object 3ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It 4ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 4ms
Request Payload.Disable Geoip Omitted Defaults To False 3ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 4ms
Request Lifecycle.No Flags Request On Init Alone 2ms
Request Lifecycle.No Flags Request On Normal Capture 163ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 5ms
Request Lifecycle.Mock Response Value Is Returned To Caller 4ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 3ms

Failures

request_payload.request_with_person_properties_device_id

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.flags_request_uses_v2_query_param

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.flags_request_hits_flags_path_not_decide

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.flags_request_omits_authorization_header

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.token_in_flags_body_matches_init

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.groups_round_trip

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.groups_default_to_empty_object

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.person_properties_distinct_id_auto_populated_when_caller_omits_it

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.disable_geoip_false_propagates_as_geoip_disable_false

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.disable_geoip_omitted_defaults_to_false

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_payload.flag_keys_to_evaluate_contains_only_requested_key

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_lifecycle.two_flag_calls_produce_two_remote_requests

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

request_lifecycle.mock_response_value_is_returned_to_caller

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

side_effect_events.get_feature_flag_captures_feature_flag_called_event

404, message='Not Found', url='http://sdk-adapter:8080/get_feature_flag'

@marandaneto
marandaneto force-pushed the fix/noop-empty-project-token branch 9 times, most recently from bed79fb to 1ea64e2 Compare April 30, 2026 11:13
@marandaneto
marandaneto marked this pull request as ready for review April 30, 2026 11:14
@marandaneto
marandaneto requested review from a team and haacked as code owners April 30, 2026 11:14
@marandaneto

Copy link
Copy Markdown
Member Author

@PostHog/team-client-libraries and @haacked i think this is what SDKs should do
can you double check so i can port this fix across SDKs? (i have a bunch of draft PRs already but some of them didnt address all stuff i found during this one)

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. src/PostHog/PostHogClient.cs, line 757-761 (link)

    P1 Double warning log in LoadFeatureFlagsAsync

    Same issue as GetRemoteConfigPayloadAsync: IsPersonalApiKeyMissing already logs the generic "personal_api_key is not configured" warning before control reaches LogWarningPersonalApiKeyRequired inside the if-body. The old call is now redundant and should be removed.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/PostHog/PostHogClient.cs
    Line: 757-761
    
    Comment:
    **Double warning log in `LoadFeatureFlagsAsync`**
    
    Same issue as `GetRemoteConfigPayloadAsync`: `IsPersonalApiKeyMissing` already logs the generic "personal_api_key is not configured" warning before control reaches `LogWarningPersonalApiKeyRequired` inside the `if`-body. The old call is now redundant and should be removed.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/PostHog/PostHogClient.cs:553-556
**Double warning log emitted when `PersonalApiKey` is missing**

`IsPersonalApiKeyMissing` already emits `LogWarningPersonalApiKeyMissing` ("PostHog personal_api_key is not configured; {MethodName} is a no-op.") before this `if`-body executes. The call to `LogWarningPersonalApiKeyRequiredForRemoteConfigPayload` on line 555 then fires a second, redundant warning for the same condition. The same pattern exists in `LoadFeatureFlagsAsync` (line 759, `LogWarningPersonalApiKeyRequired`). The old method-specific log calls should be removed now that the unified `IsPersonalApiKeyMissing` helper handles the logging, otherwise every missing-key event produces two warnings.

```suggestion
        if (IsPersonalApiKeyMissing(nameof(GetRemoteConfigPayloadAsync)))
        {
            return null;
        }
```

### Issue 2 of 2
src/PostHog/PostHogClient.cs:757-761
**Double warning log in `LoadFeatureFlagsAsync`**

Same issue as `GetRemoteConfigPayloadAsync`: `IsPersonalApiKeyMissing` already logs the generic "personal_api_key is not configured" warning before control reaches `LogWarningPersonalApiKeyRequired` inside the `if`-body. The old call is now redundant and should be removed.

```suggestion
        if (IsPersonalApiKeyMissing(nameof(LoadFeatureFlagsAsync)))
        {
            return;
        }
```

Reviews (1): Last reviewed commit: "Disable client when project token is mis..." | Re-trigger Greptile

Comment thread src/PostHog/PostHogClient.cs Outdated
@marandaneto
marandaneto force-pushed the fix/noop-empty-project-token branch 2 times, most recently from caef5ff to e8527fc Compare April 30, 2026 11:36
@marandaneto marandaneto changed the title Disable client when project token is missing fix: Disable client when project token is missing Apr 30, 2026

@haacked haacked 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.

A few suggestions in-line.

Comment thread src/PostHog/Config/PostHogOptions.cs Outdated
Comment thread src/PostHog/PostHogClient.cs Outdated
Comment thread src/PostHog/PostHogClient.cs Outdated
Comment thread src/PostHog/PostHogClient.cs
Comment thread src/PostHog/PostHogClient.cs Outdated
AllFeatureFlagsOptions? options,
CancellationToken cancellationToken)
{
if (IsDisabled(nameof(GetAllFeatureFlagsAsync)))

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.

suggestion: This method is async Task<…> but the disabled path returns synchronously, so the C# compiler still allocates the async state machine and walks ExecutionContext. GroupIdentifyAsync (line 182) shows the pattern: Task return, no async, Task.FromResult(...) on the fast path, delegate to a private async core for the real work.

Same applies to AliasAsync (148), IdentifyAsync (163), CaptureException (277), IsFeatureEnabledAsync (369), GetFeatureFlagAsync (395), GetRemoteConfigPayloadAsync (545), LoadFeatureFlagsAsync (748), GetLocalEvaluatorAsync (798).

Pairs with the cached-result singletons above to make the disabled path truly zero-allocation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Partially addressed — cached the no-op return values. I kept the async method structure intact to keep this fix focused and avoid a large control-flow refactor.

Comment thread tests/UnitTests/PostHogClientTests.cs
Comment thread tests/UnitTests/PostHogClientTests.cs
Comment thread tests/UnitTests/PostHogClientTests.cs
Comment thread tests/UnitTests/PostHogClientTests.cs
@marandaneto
marandaneto force-pushed the fix/noop-empty-project-token branch from e8527fc to 21104bc Compare May 4, 2026 09:15
@marandaneto
marandaneto force-pushed the fix/noop-empty-project-token branch from 21104bc to 2c39054 Compare May 4, 2026 09:15
@marandaneto
marandaneto requested a review from haacked May 4, 2026 09:25
@marandaneto

Copy link
Copy Markdown
Member Author

A few suggestions in-line.

Thanks and done.
can you double check?

@marandaneto
marandaneto requested a review from dustinbyrne May 5, 2026 15:17

@haacked haacked 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.

Some nits, but nothing major.

Comment thread src/PostHog/PostHogClient.cs
Comment thread tests/UnitTests/PostHogClientTests.cs
Comment thread src/PostHog/Config/PostHogOptions.cs
@marandaneto
marandaneto enabled auto-merge (squash) May 6, 2026 09:18
@marandaneto
marandaneto merged commit 293539c into main May 6, 2026
14 checks passed
@marandaneto
marandaneto deleted the fix/noop-empty-project-token branch May 6, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants