Skip to content

[BUG] null values in targeting rules for object-valued flags when there is no defaultValue should evaluate to the code default #1913

Closed

Description

@asppsa

Observed behavior

I think I have found a bug. A minimal demonstration of the issue is the following:

{
  "$schema": "https://flagd.dev/schema/v0/flags.json",
  "flags": {
    "my-object-flag": {
      "state": "ENABLED",
      "variants": {
        "on": { "payload": "hello" }
      },
      "targeting": {
        "if": [false, "on", null]
      }
    }
  }
}

If I evaluate the above with getObjectDetails (using OpenFeature in Node.js), I would expect get back whatever code default I supplied. Instead I get:

{
  value: undefined,
  reason: '',
  variant: undefined,
  flagMetadata: {},
  flagKey: 'my-object-flag'
}

I'm testing with flagd 0.14.3, installed via homebrew.

Expected Behavior

Based on https://flagd.dev/reference/flag-definitions/#targeting-rules,

If a null value is returned by the targeting rule, the defaultVariant is used. This can be useful for conditionally "exiting" targeting rules and falling back to the default (in this case the returned reason will be DEFAULT).... If defaultVariant is not defined or is null, and no variant is resolved from targeting, flagd providers will revert to the code default.

In the example, the targeting rule trivially returns null, and then because there is no defaultValue, I believe the correct behaviour should be to return the code default.

Steps to reproduce

  1. run flagd start --uri file:./test.json (with my test file above)
  2. evaluate the following node.js code:
import { FlagdProvider } from '@openfeature/flagd-provider'
import { OpenFeature } from '@openfeature/server-sdk'

await OpenFeature.setProviderAndWait(
  new FlagdProvider({
    host: '127.0.0.1',
    port: 8013,
    tls: false,
  }),
)

const client = OpenFeature.getClient()

console.dir(
  await client.getObjectDetails('my-object-flag', {}),
  { depth: null },
)

Activity

  1. added
    bugSomething isn't working
    Needs TriageThis issue needs to be investigated by a maintainer
    on Mar 27, 2026
  2. asppsa commented on Mar 27, 2026

    @asppsa
    Author

    GitHub Copilot helped me work the above out. It further claims that:

    the bug has two layers, and only the first one is object-specific:

    1. objectResponseV2 is missing SetReasonOnly entirely — so the responseV2[T] type assertion silently fails and the response is completely empty (no Reason, no Value, nothing). This only affects object evaluation.
    2. All the other SetReasonOnly implementations leave Value as nil — for string, bool, int, and float. Whether this is a problem depends on how the SDK client handles a nil proto field. In our testing, the JS SDK happened to cope with nil *string by falling back to the caller default, but the contract is still wrong — Value should be set to the zero value.

    I can make a PR with what it believes the fixes for these to be, but I don't speak Go myself, so I'm not able to vouch for the changes.

  3. self-assigned this
    on Apr 2, 2026
  4. toddbaert commented on Apr 2, 2026

    @toddbaert
    Member

    Taking a look...

  5. added a commit that references this issue on Apr 2, 2026
    6a0aeaf
  6. toddbaert commented on Apr 2, 2026

    @toddbaert
    Member

    Thanks @asppsa - it was as you said. Please see #1925

  7. asppsa commented on Apr 3, 2026

    @asppsa
    Author

    Thanks - I've tested the PR and it appears to solve the issue I was seeing 👍

  8. added a commit that references this issue on Apr 7, 2026
    c4ad877
  9. removed
    Needs TriageThis issue needs to be investigated by a maintainer
    on Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions