Flagsmith / Flagsmith/flagsmith
LaunchDarkly importer sets boolean enabled from LD 'on', ignoring served variation (inverts on-but-serving-false flags)
- Dominant language
- Python
- Stars
- 6.6k
- Forks
- 567
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 121
Description
## 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:
```python
# 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.
Contributor guide
Research direction
Start in api/integrations/launch_darkly/services.py at _create_boolean_feature_states_with_segments_identities, then compare the boolean path with the string and multivariate factories’ _summary handling. Trace how fallthrough, offVariation, and percentage rollouts are represented. Done means boolean imports use the resolved served default, with an explicit outcome for rollouts and visibility into mismatched imports.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100