Skip to content

LaunchDarkly importer sets boolean enabled from LD 'on', ignoring served variation (inverts on-but-serving-false flags) #8047

Description

@Holmus

Summary

For boolean flags, the LaunchDarkly importer maps LD's on toggle directly to Flagsmith's enabled state and ignores the flag's actual default served value (the fallthrough variation when on, or the offVariation when off).

LaunchDarkly separates "is the flag on" from "what does it serve", so a flag can be on: true and still serve false via its fallthrough (a common way to stage a flag: on, defaulting to false, with specific segments/targets receiving true). Flagsmith boolean flags have no such separation — enabled is the served value — so these flags import with an inverted state and evaluate to the opposite of what they do in LaunchDarkly.

Severity

Correctness. Imported boolean flags can evaluate to the wrong value. The mismatch is silent: nothing is written to error_messages and the import still reports success/incomplete. Any project that uses the "flag on, default off, enable via targeting" pattern can have a large fraction of its boolean flags affected.

Behaviour

For a boolean flag, the importer does:

# api/integrations/launch_darkly/services.py  (_create_boolean_feature_states_with_segments_identities)
feature_state, _ = FeatureState.objects.update_or_create(
    feature=feature,
    feature_segment=None,
    environment=environment,
    defaults={"enabled": ld_flag_config["on"]},   # <-- uses on/off toggle, not the served variation
)
FeatureStateValue.objects.update_or_create(feature_state=feature_state)  # empty value

It never reads fallthrough or offVariation, so the served variation is discarded for boolean flags. (The string/multivariate factories do resolve the served variation via _summary — only the boolean path is affected.)

Steps to reproduce

  1. In LaunchDarkly, create a boolean flag with variations [true, false].
  2. Turn the flag on, and set the default rule (fallthrough) to serve the false variation.
  3. Import the project into Flagsmith.
  4. Observe the imported flag: environment-default enabled is true, so it serves true, while the same flag serves false in LaunchDarkly.

The reverse also occurs: a flag that is off with an offVariation of true imports as enabled: false.

Expected behaviour

For a boolean flag the environment-default enabled should be the resolved default served value, not the raw toggle:

  • on: true -> enabled = value_of(fallthrough.variation)
  • on: false -> enabled = value_of(offVariation)

Suggested fix

  • In _create_boolean_feature_states_with_segments_identities, resolve the default served variation (fallthrough when on, otherwise offVariation) and set enabled to that variation's boolean value, instead of ld_flag_config["on"].
  • Decide handling for boolean flags whose default is a percentage rollout between true/false rather than a fixed variation. The boolean path currently ignores rollout entirely and flattens to the on state; it should either map the split or log a skip rather than silently dropping it.
  • Because the mismatch is otherwise silent, consider surfacing a per-flag note when a boolean flag's resolved served value differs from its on state, so already-completed (and now incorrect) imports are discoverable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions