Skip to content

feat(flagd): support custom gRPC metadata on in-process SyncFlags - #404

Open
tuhinkanti wants to merge 4 commits into
open-feature:mainfrom
tuhinkanti:feat/flagd-sync-custom-metadata
Open

feat(flagd): support custom gRPC metadata on in-process SyncFlags#404
tuhinkanti wants to merge 4 commits into
open-feature:mainfrom
tuhinkanti:feat/flagd-sync-custom-metadata

Conversation

@tuhinkanti

@tuhinkanti tuhinkanti commented Jul 24, 2026

Copy link
Copy Markdown

This PR

Closes #403

Adds a sync_metadata option to the flagd provider so callers can send custom gRPC metadata headers on the in-process SyncFlags stream.

Problem

The in-process resolver builds the gRPC metadata for the long-lived SyncFlags call in _create_metadata, which adds only the flagd-selector header (and only when a selector is set). There is no way for a caller to add other metadata headers, so infrastructure-specific headers cannot be sent on the sync stream.

Change

  • Add a sync_metadata option to FlagdProvider and Config (a sequence of (key, value) header pairs).
  • In _create_metadata, append the configured pairs to the metadata, alongside the flagd-selector header.
  • Return no metadata when neither a selector nor custom metadata is set, preserving current behavior.

Use case

A proxy can close a stream after a default request timeout. Sending a header such as x-envoy-upstream-rq-timeout-ms: 0 on the long-lived sync stream keeps the stream open.

Compatibility

Backward compatible. Custom metadata is added only when sync_metadata is set. The default behavior does not change.

Tests

New tests cover config passthrough and metadata construction (selector-only, custom-only, both together, and disabled).

$ pytest tests/test_config.py tests/test_grpc_watcher.py -k "sync_metadata or metadata" -v

tests/test_config.py::test_sync_metadata_passthrough PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_custom_sync_metadata_appended PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_custom_sync_metadata_without_selector PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_listen_with_sync_metadata_and_sync_context PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_listen_with_sync_metadata_disabled_in_config PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_listen_with_sync_metadata_only PASSED
tests/test_grpc_watcher.py::TestGrpcWatcher::test_selector_passed_via_both_metadata_and_body PASSED

Full suite for the two touched test files:

$ pytest tests/test_config.py tests/test_grpc_watcher.py

20 passed

@tuhinkanti
tuhinkanti requested review from a team as code owners July 24, 2026 01:32
@github-actions
github-actions Bot requested review from aepfli and federicobond July 24, 2026 01:32
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 37f7087d-2353-40c6-a307-87e6b2b7f951

📥 Commits

Reviewing files that changed from the base of the PR and between 81adc6e and ebbb279.

📒 Files selected for processing (5)
  • providers/openfeature-provider-flagd/README.md
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py
  • providers/openfeature-provider-flagd/tests/test_grpc_resolver.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Flagd now accepts optional synchronous gRPC client interceptors and custom channel credentials. Config normalizes interceptors, the provider forwards them, and both gRPC resolvers apply them. Unexpected stream errors now trigger logging and reconnect backoff.

Changes

Flagd gRPC Interceptors

Layer / File(s) Summary
Configure and propagate client interceptors
providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py, providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py, providers/openfeature-provider-flagd/tests/test_config.py, providers/openfeature-provider-flagd/README.md
Config accepts and normalizes client interceptors. FlagdProvider forwards them. Documentation and tests cover defaults, metadata constraints, channel credentials, and positional compatibility.
Apply interceptors and channel credentials
providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py, providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py, providers/openfeature-provider-flagd/tests/test_grpc_resolver.py, providers/openfeature-provider-flagd/tests/test_grpc_watcher.py
Both resolvers apply configured interceptors to generated channels. The RPC resolver uses explicit channel credentials before TLS settings. Tests cover metadata injection, invalid interceptors, custom credentials, and raw-channel defaults.
Handle unexpected stream errors
providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/grpc.py, providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py, providers/openfeature-provider-flagd/tests/test_grpc_resolver.py, providers/openfeature-provider-flagd/tests/test_grpc_watcher.py
Unexpected stream exceptions are logged. Active streams reconnect after backoff. GrpcWatcher delegates one stream iteration to _listen_once. Tests cover both listeners.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to ebbb2

The change adds configurable synchronous gRPC interceptors and channel credentials, applies them to both flagd resolver paths, and adds stream reconnect handling. No concrete merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant FlagdProvider
  participant Config
  participant GrpcResolver
  participant GrpcWatcher
  participant FlagSyncServiceStub
  FlagdProvider->>Config: pass client_interceptors
  Config->>GrpcResolver: provide normalized interceptors
  Config->>GrpcWatcher: provide normalized interceptors
  GrpcResolver->>GrpcResolver: create and wrap channel
  GrpcWatcher->>GrpcWatcher: create and wrap channel
  GrpcWatcher->>FlagSyncServiceStub: call SyncFlags through interceptor
Loading

Suggested reviewers: toddbaert

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes do not implement the primary API requirements from issue #403 [#403]. They add client_interceptors instead of sync_metadata, do not update _create_metadata to append configured pairs, and … Implement sync_metadata in Config and FlagdProvider as a sequence of header pairs. Update _create_metadata in the in-process resolver to preserve flagd-selector and append configured metadata. Keep the existing empty-metadata behavior when …
Out of Scope Changes check ⚠️ Warning The pull request includes changes unrelated to issue #403 [#403], including custom channel credentials and broad EventStream reconnect/error-handling changes in the gRPC resolver. The client-intercept… Remove unrelated channel-credential and EventStream error-handling changes, or link them to separate issues. Keep only changes required to add sync_metadata support to the in-process SyncFlags stream.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 7 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary functional goal: adding custom gRPC metadata support to the in-process SyncFlags stream. The implementation uses client interceptors rather than a sync_metadata…
Description check ✅ Passed The description addresses custom metadata for the in-process SyncFlags stream and directly relates to issue #403. However, it describes a sync_metadata implementation that does not match the client-in…
Full details: Linked Issues check

Explanation

The changes do not implement the primary API requirements from issue #403 [#403]. They add client_interceptors instead of sync_metadata, do not update _create_metadata to append configured pairs, and do not provide the requested Config and FlagdProvider sync_metadata options.

Resolution

Implement sync_metadata in Config and FlagdProvider as a sequence of header pairs. Update _create_metadata in the in-process resolver to preserve flagd-selector and append configured metadata. Keep the existing empty-metadata behavior when neither option is configured. Add tests for selector-only, custom-only, combined, and default cases.

Full details: Out of Scope Changes check

Explanation

The pull request includes changes unrelated to issue #403 [#403], including custom channel credentials and broad EventStream reconnect/error-handling changes in the gRPC resolver. The client-interceptor API also expands the scope beyond the requested sync_metadata option.

Full details: Docstring Coverage

Explanation

Docstring coverage is 5.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 35 functions across 7 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py`:
- Line 107: Preserve positional constructor compatibility by moving
sync_metadata after fatal_status_codes in both public constructors:
providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py
lines 107-107 and
providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py
lines 68-68. Add a regression test confirming positional fatal_status_codes
arguments still configure fatal status handling correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 67aff781-dac2-4bef-b0b2-89d407bc7082

📥 Commits

Reviewing files that changed from the base of the PR and between cde03cc and fd2c118.

📒 Files selected for processing (5)
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/provider.py
  • providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/connector/grpc_watcher.py
  • providers/openfeature-provider-flagd/tests/test_config.py
  • providers/openfeature-provider-flagd/tests/test_grpc_watcher.py

Add a `sync_metadata` option to FlagdProvider / Config that appends
user-supplied gRPC metadata headers to every in-process SyncFlags call,
alongside the provider-managed flagd-selector header.

This lets callers inject infrastructure-specific headers on the
long-lived sync stream -- e.g. `x-envoy-upstream-rq-timeout-ms: 0` to
disable a proxy request timeout that would otherwise sever the
stream after its default deadline.

Keep `fatal_status_codes` as the last positional parameter in both
public constructors so existing positional callers are unaffected, and
add a regression test covering that ordering.

Signed-off-by: Tuhin Sharma <tsharma@salesforce.com>
@tuhinkanti
tuhinkanti force-pushed the feat/flagd-sync-custom-metadata branch from fd2c118 to 9dcd715 Compare July 24, 2026 01:48
@toddbaert

toddbaert commented Aug 17, 2026

Copy link
Copy Markdown
Member

Thanks for this; the problem is valid, proxies killing the long-lived sync stream is a realistic issue.

My worry is that sync_metadata is pretty specific. Custom headers aren't really a flagd concept, and if we add this I suspect we'll keep getting "can we also set X on the channel" requests.

Since grpcio already handles this, what about exposing a gRPC interceptor option instead? An interceptor that adds your x-envoy-upstream-rq-timeout-ms: 0 header covers your case and any future one, without flagd growing a new option each time. It also matches what Java and Go already do, middleware stays outside the normal flagd options.

Docs:

Would you be up for reworking it that way? Happy to help.

@tuhinkanti

Copy link
Copy Markdown
Author

Let me look and get back to you

Tuhin Sharma added 2 commits August 17, 2026 15:56
Keep both sync_metadata test setup and the new fatal_status_codes / reconnect backoff coverage from main.
Signed-off-by: Tuhin Sharma <tsharma@salesforce.com>
@tuhinkanti
tuhinkanti force-pushed the feat/flagd-sync-custom-metadata branch from 268ad37 to 81adc6e Compare September 3, 2026 22:21
Signed-off-by: Tuhin Sharma <tsharma@salesforce.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flagd provider: allow custom gRPC metadata on the in-process SyncFlags stream

4 participants