Skip to content

feat: multi-provider strategies skip providers returning reason=DISABLED#386

Open
jonathannorris wants to merge 3 commits into
mainfrom
feat/multi-provider-disabled-reason
Open

feat: multi-provider strategies skip providers returning reason=DISABLED#386
jonathannorris wants to merge 3 commits into
mainfrom
feat/multi-provider-disabled-reason

Conversation

@jonathannorris
Copy link
Copy Markdown
Member

@jonathannorris jonathannorris commented May 21, 2026

Summary

reason=DISABLED means a provider has intentionally turned a flag off; it's an operational state, not a resolved value. In a Multi-Provider context, this is semantically equivalent to FLAG_NOT_FOUND — the provider isn't returning a value, so the Multi-Provider should fall through to the next one.

This is a widespread pattern across the OpenFeature provider ecosystem. Flipt (Go, JS, .NET), Flagsmith (Go, Java, JS, .NET), LaunchDarkly, Optimizely, GO Feature Flag, OFREP, DevCycle, and the Dynatrace providers all return reason=DISABLED when a flag is intentionally off.

This PR updates the three standard strategies to treat reason=DISABLED consistently as a fall-through signal, the same as FLAG_NOT_FOUND:

  • First Match: also skips providers returning reason=DISABLED
  • First Successful: also skips providers returning reason=DISABLED when looking for a provider with a value
  • Comparison: excludes providers returning reason=DISABLED from the comparison

The immediate trigger for this change is the flagd disabled flag evaluation ADR, which moves flagd from returning a FLAG_DISABLED error to a successful reason=DISABLED result. Without this spec update, that change would silently break Multi-Provider fall-through for flagd users.

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the documentation for the Multi-Provider strategies—First Match, First Successful, and Comparison—to include logic for skipping providers that are intentionally disabled (reason=DISABLED). A review comment suggests clarifying the description of the First Successful Strategy by removing the redundant mention of FLAG_NOT_FOUND, as it is already classified as an error and excluded from successful results.

Comment thread specification/appendix-a-included-utilities.md Outdated
@erka
Copy link
Copy Markdown
Member

erka commented May 21, 2026

If people don’t use flagd, they’ll be affected by this change for no reason. It would be better to simply explain how to create a custom strategy to preserve the previous behavior for those who use flagd and really want old results.

Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@jonathannorris
Copy link
Copy Markdown
Member Author

Good point worth addressing. This change isn't specific to flagd. reason=DISABLED is returned by a broad set of providers across the ecosystem: Flipt (Go, JS, .NET), Flagsmith (Go, Java, JS, .NET), LaunchDarkly, Optimizely, GO Feature Flag, and OFREP all explicitly return DISABLED when a flag is intentionally off. DevCycle and the Dynatrace providers do as well.

More broadly, I think the right question is: should reason=DISABLED be a fall-through by default in Multi-Provider? I think it should. DISABLED means "this provider has intentionally turned this flag off"; it's an operational state, not a resolved value. The provider isn't saying "here's your answer," it's saying "I have nothing for you right now." That's semantically the same as FLAG_NOT_FOUND from the Multi-Provider's perspective. Falling through to the next provider is the behavior that makes sense in migration, rollout, and multi-source scenarios, which is exactly what Multi-Provider is designed for.

Anyone who wants DISABLED to be a terminal result can implement that via a custom strategy.

@jonathannorris jonathannorris marked this pull request as ready for review May 25, 2026 14:52
@jonathannorris jonathannorris requested a review from a team as a code owner May 25, 2026 14:52
@jonathannorris jonathannorris requested a review from Copilot May 25, 2026 14:57
Copy link
Copy Markdown

@suthar26 suthar26 left a comment

Choose a reason for hiding this comment

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

This looks good

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Multi-Provider strategy semantics in the spec so that provider results indicating an intentionally disabled flag (reason = DISABLED) are treated as “no value” and therefore fall through to subsequent providers, consistent with FLAG_NOT_FOUND.

Changes:

  • Update First Match strategy to skip providers that return reason=DISABLED (in addition to FLAG_NOT_FOUND).
  • Update First Successful strategy to skip reason=DISABLED when searching for a provider result to use.
  • Update Comparison strategy to exclude FLAG_NOT_FOUND and reason=DISABLED providers from comparisons.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread specification/appendix-a-included-utilities.md
Comment thread specification/appendix-a-included-utilities.md Outdated
Comment thread specification/appendix-a-included-utilities.md
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com>
@toddbaert
Copy link
Copy Markdown
Member

I agree with the idea that this change makes sense, but I understand @erka 's hesitation. After seeing the comment from @jonathannorris here, I think it's something we probably want, but I have to agree this is a breaking change, though admittedly with a "secondary" part of the SDK.

It's pretty niche, and I do think the newly proposed behavior follows the principle of least astonishment.

cc @thomaspoignant @beeme1mr @kinyoklion @lukas-reining @oxddr

cc @erka

@askpt
Copy link
Copy Markdown
Member

askpt commented May 25, 2026

I agree with the idea that this change makes sense, but I understand @erka 's hesitation. After seeing the comment from @jonathannorris here, I think it's something we probably want, but I have to agree this is a breaking change, though admittedly with a "secondary" part of the SDK.

It's pretty niche, and I do think the newly proposed behavior follows the principle of least astonishment.

cc @thomaspoignant @beeme1mr @kinyoklion @lukas-reining @oxddr

cc @erka

I would prefer to have a separate strategy for disabled status. This clearly breaks behaviour for existing customers. Something like First Match Strategy with Disabled and Key not Found and First Successful Strategy with Disabled and Key not Found. That way we keep the previous behaviour untouched.

Now, if we treat as a bug in the Multi-provider spec, I have no objections. The intent in this case shows it as a new feature, hence my hesitation with this one.

@jonathannorris
Copy link
Copy Markdown
Member Author

jonathannorris commented May 25, 2026

I would prefer to have a separate strategy for disabled status. This clearly breaks behaviour for existing customers. Something like First Match Strategy with Disabled and Key not Found and First Successful Strategy with Disabled and Key not Found. That way we keep the previous behaviour untouched.

Now, if we treat as a bug in the Multi-provider spec, I have no objections. The intent in this case shows it as a new feature, hence my hesitation with this one.

I'd argue this is a bug rather than a new feature. The Multi-Provider spec already defines FLAG_NOT_FOUND as a fall-through condition: "this provider has no value for this flag." The OpenFeature spec defines DISABLED as "the resolved value was the result of the flag being disabled in the management system", which is semantically the same thing. The provider evaluated the flag and intentionally produced no meaningful result. The Multi-Provider spec simply never addressed this case.

Consider the expected end-user experience. If my-feature doesn't exist in Provider A, Multi-Provider falls through to Provider B. If my-feature is disabled in Provider A, why would the behaviour be any different? From the user's perspective, Provider A is saying "I have nothing for you" in both cases. Stopping at Provider A and returning a code default, rather than checking Provider B, would be surprising.

I'm personally pretty against sprawling to more strategies, that feels like punting on making a decision if this is an issue or not.

@erka
Copy link
Copy Markdown
Member

erka commented May 27, 2026

There is an option 2 in the Considered options section of the flagd ADR.

Successful evaluation with reason=DISABLED, returning the configured defaultVariant value.

If flagd implements that in future? Or what if someone else is doing it? If the flag is off, it could return a different preconfigured value than the one hardcoded in the code. The adverse impact of this change may be broader than leaving it as it is.

Copy link
Copy Markdown
Member

@lukas-reining lukas-reining left a comment

Choose a reason for hiding this comment

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

That's semantically the same as FLAG_NOT_FOUND from the Multi-Provider's perspective.

I do not agree that this is semantically the same, especially for First Match.
Flag not found really means that there is no information at all, while disabled can have the lastest information but it specifically states being disabled.

More broadly, I think the right question is: should reason=DISABLED be a fall-through by default in Multi-Provider? I think it should. DISABLED means "this provider has intentionally turned this flag off"; it's an operational state, not a resolved value.

If that is true, we would not have to have both from my point of view.
To me DISABLED can definitely carry the current value.

Falling through to the next provider is the behavior that makes sense in migration, rollout, and multi-source scenarios, which is exactly what Multi-Provider is designed for.

I agree with this. To me, it feels reasonable to do this change.
But also it is a breaking change to me.

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.

7 participants