open-feature / open-feature/go-sdk

[BUG] a PROVIDER_CONFIGURATION_CHANGED event clears FATAL and silently resumes evaluation

Open
#582 0 comments 0 reactions 0 assignees View on GitHub
bug Needs Triage
Dominant language
Go
Stars
250
Forks
62
Avg merge
2d 3h
Merged PRs (30d)
25

Description

## Observed behavior

A `PROVIDER_CONFIGURATION_CHANGED` event unconditionally moves a provider out of `FATAL` and back to `READY`, silently re-enabling flag evaluation against a provider that is fatally broken.

`statesMap` maps the event to `ReadyState` with no regard for the current state:

https://github.com/open-feature/go-sdk/blob/5d64ff9c5b4f362bc26cb8732d589155e9c6e2bd/openfeature/openfeature_api.go#L518-L528

and `triggerEvent` stores that state unconditionally:

https://github.com/open-feature/go-sdk/blob/5d64ff9c5b4f362bc26cb8732d589155e9c6e2bd/openfeature/event_executor.go#L316-L330

This is reachable with a single provider, but the multi-provider makes it considerably more likely: the event that clears the FATAL can come from a *different, perfectly healthy* provider, with no recovery whatsoever on the fatal one.

Running two providers behind a multi-provider, where the fake provider deliberately resolves to a non-default value so the last column distinguishes "evaluation skipped" from "provider consulted":

```
initial: client.State() = "READY" mp.Status() = "READY"
after A emits FATAL: client.State() = "FATAL" mp.Status() = "FATAL" eval -> default (skipped)
after B emits CONFIG_CHANGED: client.State() = "READY" mp.Status() = "FATAL" eval -> provider consulted
```

The multi-provider's own `Status()` still reports FATAL, so the SDK-level state and the provider's state disagree, and evaluations that should return the default silently start hitting the broken provider again.

## Expected Behavior

[Appendix A](https://github.com/open-feature/spec/blob/main/specification/appendix-a-included-utilities.md#multi-provider-status): *"The only statuses which affect evaluation behavior at the SDK client level are FATAL and NOT_READY. If a provider is in either of these states, evaluation will be skipped by the client and the default value will be returned."*

FATAL is terminal. A configuration-change event should not be able to clear it: the provider should stay FATAL and evaluation should keep short-circuiting to the default until the provider is re-initialized. `ProviderReady` is the event that legitimately signals recovery.

## Steps to reproduce

Two providers behind a multi-provider, both implementing `EventChannel()`:

```go
mp, _ := multi.NewProvider(multi.StrategyFirstMatch,
multi.WithProvider("a", a), multi.WithProvider("b", b))
_ = of.SetProviderAndWait(mp)
client := of.NewClient("domain")

// A goes fatal -> client is FATAL, evaluation correctly returns the default
a.ch <- of.Event{EventType: of.ProviderError, ProviderEventDetails: of.ProviderEventDetails{
ErrorCode: of.ProviderFatalCode, EventMetadata: map[string]any{},
}}

// healthy B emits a routine config change -> client flips to READY
b.ch <- of.Event{EventType: of.ProviderConfigChange, ProviderEventDetails: of.ProviderEventDetails{
EventMetadata: map[string]any{},
}}

fmt.Println(client.State(), mp.Status()) // "READY" "FATAL"
// client.Boolean(...) now resolves through the fatally-broken provider
```

Noticed while reviewing #580, which makes FATAL reachable in the multi-provider for the first time and so makes this observable there.

*AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: 2.1.236.*

Contributor guide

Open the contributing guide

Research direction

Start with the statesMap handling in openfeature/openfeature_api.go and the unconditional state storage in openfeature/event_executor.go. Trace how ProviderConfigChange and ProviderReady affect client state and evaluation, then add regression coverage showing that configuration changes do not clear FATAL while recovery still works through ProviderReady.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.