<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>FlagLint | FlagLint Blog</title><description>FlagLint documentation and engineering notes for auditing LaunchDarkly Node.js SDK usage, previewing safe OpenFeature migrations, and enforcing the boundary in CI.</description><link>https://flaglint.dev/</link><language>en</language><item><title>Five LaunchDarkly SDK Patterns That Block Automatic Migration to OpenFeature</title><link>https://flaglint.dev/blog/five-patterns-that-block-migration/</link><guid isPermaLink="true">https://flaglint.dev/blog/five-patterns-that-block-migration/</guid><pubDate>Sat, 06 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Run &lt;code dir=&quot;auto&quot;&gt;flaglint migrate ./src --dry-run&lt;/code&gt; and you will see two kinds of results: call
sites with a generated diff and call sites marked &lt;code dir=&quot;auto&quot;&gt;skip — manual review required&lt;/code&gt;.
The skipped calls are not bugs in the tool. They are patterns where a mechanical
rewrite would change runtime behavior in ways the tool cannot prove are safe.&lt;/p&gt;
&lt;p&gt;This article covers the five patterns that produce skips and what you need to do for each.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;what-makes-a-call-automatable&quot;&gt;What makes a call automatable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;FlagLint rewrites a call automatically only when it can prove three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The flag key is a static string literal.&lt;/li&gt;
&lt;li&gt;The fallback value type is known (&lt;code dir=&quot;auto&quot;&gt;boolean&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;string&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;number&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A verified OpenFeature client binding is present in scope.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any proof fails, the call is left unchanged and flagged for manual review. The
conservative stance is intentional: a wrong rewrite is worse than no rewrite.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-1--dynamic-flag-keys&quot;&gt;Pattern 1 — Dynamic flag keys&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key comes from a variable&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;experimentGroup&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;A&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key comes from a function call&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;numberVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getFlagKey&lt;/span&gt;&lt;span&gt;(user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// key is assembled at runtime&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;variant&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;feature-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;-rollout&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;control&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature’s evaluation methods are structurally identical to LaunchDarkly’s for
this case — both take &lt;code dir=&quot;auto&quot;&gt;(key, defaultValue, context)&lt;/code&gt;. But if the key is dynamic,
FlagLint cannot know which flag is being evaluated, which means it cannot verify
that the OpenFeature provider has that flag configured, cannot determine the correct
return type, and cannot guarantee the fallback default is the right type for that
specific flag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Extract the dynamic selection into an explicit switch or map, then call evaluation
with a known static key per branch:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;numberVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;getFlagKey&lt;/span&gt;&lt;span&gt;(user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;FLAG_BY_TIER&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Record&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt; = {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;free: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-free-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pro: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-pro-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enterprise: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-enterprise-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;FLAG_BY_TIER&lt;/span&gt;&lt;span&gt;[user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tier&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; ?? &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;discount-free-tier&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;discount&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getNumberValue&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Once the key is static in each branch, the surrounding calls become automatable in
the next &lt;code dir=&quot;auto&quot;&gt;migrate&lt;/code&gt; run.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-2--detail-evaluations&quot;&gt;Pattern 2 — Detail evaluations&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.value — the evaluated value&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.reason — WHY it was that value (RULE_MATCH, FALLTHROUGH, etc.)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// detail.variationIndex — index into the flag&apos;s variation list&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-experiment&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;control&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature has an equivalent — &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails()&lt;/code&gt; returns a &lt;code dir=&quot;auto&quot;&gt;{ value, reason, errorCode }&lt;/code&gt; object. But the reason structure is different. LaunchDarkly’s
&lt;code dir=&quot;auto&quot;&gt;EvaluationReason&lt;/code&gt; includes &lt;code dir=&quot;auto&quot;&gt;ruleId&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ruleIndex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;bigSegmentsStatus&lt;/code&gt;, and
&lt;code dir=&quot;auto&quot;&gt;prerequisiteKey&lt;/code&gt;. OpenFeature’s &lt;code dir=&quot;auto&quot;&gt;ResolutionDetails&lt;/code&gt; uses a smaller vocabulary
(&lt;code dir=&quot;auto&quot;&gt;CACHED&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;DEFAULT&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ERROR&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SPLIT&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;STATIC&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;TARGETING_MATCH&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;UNKNOWN&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Code that consumes &lt;code dir=&quot;auto&quot;&gt;detail.reason.kind === &apos;RULE_MATCH&apos;&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;detail.reason.ruleId&lt;/code&gt;
will need to be updated alongside the evaluation call. FlagLint cannot safely
generate that transformation because the consuming code varies too much.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Migrate these calls manually. For each &lt;code dir=&quot;auto&quot;&gt;variationDetail&lt;/code&gt; call:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace the evaluation with &lt;code dir=&quot;auto&quot;&gt;getBooleanDetails()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;getStringDetails()&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;Update any consumers of &lt;code dir=&quot;auto&quot;&gt;reason.kind&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;reason.ruleId&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;variationIndex&lt;/code&gt; to use OpenFeature’s reason vocabulary.&lt;/li&gt;
&lt;li&gt;Add tests that cover the specific reason codes your business logic depends on.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (detail&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;RULE_MATCH&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rule-matched&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;); }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanDetails&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (detail&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;TARGETING_MATCH&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;rule-matched&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;); }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-3--bulk-state-calls&quot;&gt;Pattern 3 — Bulk state calls&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Server-side bootstrap — sends all flag values to the client&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlagsState&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Common in SSR: inject all flags into the page for client-side hydration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;locals&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;flags&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature has no equivalent to &lt;code dir=&quot;auto&quot;&gt;allFlags()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;allFlagsState()&lt;/code&gt;. The OpenFeature
specification deliberately does not include bulk evaluation — providers are expected
to surface individual flags, and bulk retrieval is a vendor-specific concern.&lt;/p&gt;
&lt;p&gt;The LaunchDarkly OpenFeature provider does not expose &lt;code dir=&quot;auto&quot;&gt;allFlags&lt;/code&gt; through the
OpenFeature client interface. If your code relies on bulk evaluation to bootstrap a
client-side SDK or build a flag snapshot, that architecture requires rethinking, not
just rewriting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option A — Enumerate the flags explicitly.&lt;/strong&gt; If you know which flags are needed
at the injection point, evaluate them individually and bundle the results:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;FLAGS_TO_BOOTSTRAP&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;show-banner&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;new-pricing&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; as const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagSnapshot&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromEntries&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;FLAGS_TO_BOOTSTRAP&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(key, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, ctx),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Option B — Use the LaunchDarkly provider directly for bootstrapping.&lt;/strong&gt; The
OpenFeature provider wraps the LD Node.js SDK. You can access the underlying client
for the specific bootstrap call while migrating all other evaluation calls to
OpenFeature:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { LaunchDarklyProvider } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;@launchdarkly/openfeature-node-server&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LaunchDarklyProvider&lt;/span&gt;&lt;span&gt;(process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LD_SDK_KEY&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; OpenFeature&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setProviderAndWait&lt;/span&gt;&lt;span&gt;(provider);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Access the underlying LD client only for bulk bootstrap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;(provider&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getClient&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// type-cast needed; provider internals are not public API&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlags&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Option B is a transitional pattern. The goal is to eliminate it once you’ve
enumerated the flags that actually need bootstrapping.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-4--configured-wrappers&quot;&gt;Pattern 4 — Configured wrappers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A shared evaluation helper used across services&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Custom wrapper that adds logging and metrics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;evaluateFlag&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fallback&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;variation&lt;/span&gt;&lt;span&gt;(key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fallback);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;metrics&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;flag.evaluation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; { key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; result });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FlagLint detects wrappers configured in &lt;code dir=&quot;auto&quot;&gt;.flaglintrc&lt;/code&gt; under the &lt;code dir=&quot;auto&quot;&gt;wrappers&lt;/code&gt; key.
When a wrapper is detected, the call is surfaced in the audit and scan output but
never auto-rewritten — because rewriting the call site does not solve the problem.
The wrapper itself contains the direct LD SDK call that needs to change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Migrate the wrapper implementation, not the call sites. The call sites stay the same;
only the internals of the wrapper change:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;LDContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — wrapper now delegates to OpenFeature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;EvaluationContext&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(flagKey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;After the wrapper implementation is migrated, configure FlagLint to recognise the
wrapper’s result type so downstream &lt;code dir=&quot;auto&quot;&gt;scan&lt;/code&gt; output is accurate:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;wrappers&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;isFeatureEnabled&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;evaluateFlag&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Wrappers that accept a dynamic &lt;code dir=&quot;auto&quot;&gt;flagKey&lt;/code&gt; parameter will still appear in reports —
that is correct behaviour. The scanner surfaces them for the same reason it surfaces
dynamic keys: it cannot prove which flag is being evaluated.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;pattern-5--unknown-fallback-types-jsonvariation&quot;&gt;Pattern 5 — Unknown fallback types (&lt;code dir=&quot;auto&quot;&gt;jsonVariation&lt;/code&gt;)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What it looks like:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Untyped JSON — fallback type is object but the shape is unknown&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, {}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Typed JSON with a complex or union shape&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;rules&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;routing-rules&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, { routes:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Why it blocks migration:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenFeature’s equivalent is &lt;code dir=&quot;auto&quot;&gt;getObjectValue()&lt;/code&gt;, which returns &lt;code dir=&quot;auto&quot;&gt;JsonValue&lt;/code&gt; — a union
of &lt;code dir=&quot;auto&quot;&gt;string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }&lt;/code&gt;.
When the fallback is &lt;code dir=&quot;auto&quot;&gt;{}&lt;/code&gt; or another untyped object, FlagLint cannot determine the
correct generic type to use, and it cannot verify that the calling code handles the
&lt;code dir=&quot;auto&quot;&gt;JsonValue&lt;/code&gt; type correctly rather than a narrower application-specific type.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to resolve:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add explicit type annotations to the fallback and the result, then migrate manually:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Define the expected shape&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; PricingConfig {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;basePrice&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;currency&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tiers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;; discount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; }[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PricingConfig&lt;/span&gt;&lt;span&gt; = { basePrice: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, currency: &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;USD&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, tiers:&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;jsonVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getObjectValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;pricing-config&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;PricingConfig&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The explicit cast is safe because the provider returns whatever LaunchDarkly sends,
and the schema is defined in the LaunchDarkly dashboard. If the shape might not
match, add a runtime validator (Zod works well here) at the call site.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;seeing-the-full-breakdown-before-you-start&quot;&gt;Seeing the full breakdown before you start&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before migrating, run &lt;code dir=&quot;auto&quot;&gt;flaglint audit ./src&lt;/code&gt; to see how many calls fall into each
category and why:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Audit complete: 18 flags — 5 high risk, 13 medium risk&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| Flag Key              | Risk   | Usages | Reasons                           |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;|-----------------------|--------|--------|-----------------------------------|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| &amp;#x3C;dynamic key&gt;         | High   | 9      | key cannot be resolved statically |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-experiment   | High   | 1      | detail evaluation                 |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| *                     | High   | 1      | bulk call                         |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| &amp;#x3C;wrappers&gt;            | High   | 4      | configured wrapper                |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| pricing-config        | Medium | 1      | json — unknown shape              |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;| checkout-v2           | Medium | 1      | safely automatable                |&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The five patterns above account for every high-risk category. Resolving them
one at a time — starting with wrappers, then dynamic keys — progressively reduces
the manual review surface until &lt;code dir=&quot;auto&quot;&gt;migrate --apply&lt;/code&gt; can handle the rest automatically.&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>migration</category><category>nodejs</category><category>devops</category></item><item><title>After the LaunchDarkly Outage: Adding a Vendor-Neutral Abstraction Without a Full Migration</title><link>https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/</link><guid isPermaLink="true">https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/</guid><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A provider outage can expose how deeply application code depends on a single
feature-flag SDK. OpenFeature creates a neutral application boundary without
forcing teams to abandon LaunchDarkly.&lt;/p&gt;
&lt;p&gt;This article walks through the local audit, migration preview, and CI enforcement
path that lets teams add that boundary incrementally.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-vendor-lock-in-problem&quot;&gt;The Vendor Lock-In Problem&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Direct SDK calls look like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; LaunchDarkly &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;launchdarkly-node-server-sdk&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;LaunchDarkly&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt;(process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LD_SDK_KEY&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Your application code is coupled to LaunchDarkly’s API surface.
Switching providers means rewriting every evaluation call.&lt;/p&gt;
&lt;p&gt;OpenFeature decouples this. Your application calls the OpenFeature API.
The provider (LaunchDarkly, or anything else) is a configuration detail.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-migration-that-stalls&quot;&gt;The Migration That Stalls&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most teams agree to add OpenFeature. Most migrations take 6+ weeks
and nearly break production at least once.&lt;/p&gt;
&lt;p&gt;The reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No inventory of where direct SDK calls live&lt;/li&gt;
&lt;li&gt;Argument-order differences cause subtle production bugs&lt;/li&gt;
&lt;li&gt;Phased migrations partially reverse when new engineers join&lt;/li&gt;
&lt;li&gt;No CI enforcement to prevent new direct calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of those reasons is an API difference that breaks even careful rewrites — &lt;a href=&quot;https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/&quot;&gt;the argument-order trap between LaunchDarkly and OpenFeature →&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-flaglint-does&quot;&gt;What FlagLint Does&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before you can migrate, you need to know what you’re migrating.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AST-based inventory of every direct LaunchDarkly SDK call.
File, line, call type, flag key, whether it’s safely automatable.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Reviewable diffs for safe call sites. Argument order corrected.
Dynamic keys, detail methods, and bulk calls reported for manual review.&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;CI gate. Fails the build if any new direct LD call appears.
The migration doesn’t rot.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;launchdarkly-stays-as-your-provider&quot;&gt;LaunchDarkly Stays As Your Provider&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is not a migration away from LaunchDarkly.&lt;/p&gt;
&lt;p&gt;LaunchDarkly offers an official OpenFeature provider. After the migration,
LaunchDarkly still evaluates your flags — you’re just calling the
OpenFeature API instead of the LaunchDarkly SDK directly.&lt;/p&gt;
&lt;p&gt;The difference: if you need to switch providers, you change the provider
configuration. Your application code doesn’t change.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://flaglint.dev/docs/quickstart&quot;&gt;Start with the scan&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://github.com/flaglint/flaglint&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href=&quot;https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/&quot;&gt;Why LaunchDarkly → OpenFeature Migrations Break in Production →&lt;/a&gt;&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>vendor-lock-in</category><category>nodejs</category><category>devops</category></item><item><title>Why LaunchDarkly → OpenFeature Migrations Break in Production</title><link>https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/</link><guid isPermaLink="true">https://flaglint.dev/blog/launchdarkly-openfeature-argument-order-bug/</guid><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;LaunchDarkly and OpenFeature both evaluate flags with three arguments, but the
fallback and context positions are reversed. A naive codemod can produce
valid-looking code that silently changes runtime behavior.&lt;/p&gt;
&lt;p&gt;This article shows the argument-order trap and why FlagLint uses AST analysis
before rewriting any call site.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;the-agreement-that-takes-30-minutes&quot;&gt;The Agreement That Takes 30 Minutes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Teams agree on OpenFeature quickly. It makes sense — vendor-neutral,
CNCF-backed, clean abstraction. The decision is easy.&lt;/p&gt;
&lt;p&gt;The migration is not.&lt;/p&gt;
&lt;p&gt;Halfway through, most teams hit a bug that looks like this in production:
a subset of users sees the wrong feature state. The flag evaluation
is returning unexpected values. Everything looked correct in code review.&lt;/p&gt;
&lt;p&gt;The cause is almost always the same thing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-argument-order-trap&quot;&gt;The Argument-Order Trap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;LaunchDarkly and OpenFeature share method names but differ in argument order:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// LaunchDarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey, context, fallback)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// OpenFeature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(flagKey, fallback, context)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; are swapped.&lt;/p&gt;
&lt;p&gt;A search-and-replace migration silently puts &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; where &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt;
should be, and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; where &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt; should be — across every call
site in your codebase.&lt;/p&gt;
&lt;p&gt;In production: users in your evaluation context see the fallback value.
In code review: the signature looks correct because the argument count matches.
In the post-mortem: nobody can identify when it was introduced.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-grep-misses-it&quot;&gt;Why Grep Misses It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The typical manual approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Search for &lt;code dir=&quot;auto&quot;&gt;launchdarkly-node-server-sdk&lt;/code&gt; imports&lt;/li&gt;
&lt;li&gt;Find all &lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; references&lt;/li&gt;
&lt;li&gt;Search-and-replace method names&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This finds the calls. It does not understand argument semantics.&lt;/p&gt;
&lt;p&gt;A grep-based migration will correctly rename &lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt; to
&lt;code dir=&quot;auto&quot;&gt;getBooleanValue&lt;/code&gt; and miss the argument order entirely. The test suite
often misses it too because the values are both valid types — &lt;code dir=&quot;auto&quot;&gt;context&lt;/code&gt;
and &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; are both objects.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-ast-analysis-catches&quot;&gt;What AST Analysis Catches&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Abstract Syntax Tree (AST) analysis parses your code the same way a
compiler does. It doesn’t match text — it understands structure.&lt;/p&gt;
&lt;p&gt;For a call like:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;AST analysis identifies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The import binding (&lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;launchdarkly-node-server-sdk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The method name (&lt;code dir=&quot;auto&quot;&gt;boolVariation&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The argument at position 0: flag key (&lt;code dir=&quot;auto&quot;&gt;&apos;checkout-v2&apos;&lt;/code&gt; — string literal)&lt;/li&gt;
&lt;li&gt;The argument at position 1: context (&lt;code dir=&quot;auto&quot;&gt;ctx&lt;/code&gt; — object reference)&lt;/li&gt;
&lt;li&gt;The argument at position 2: fallback (&lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; — boolean literal)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When generating the OpenFeature equivalent, it knows to produce:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;openFeatureClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getBooleanValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Argument 2 goes to position 1. Argument 1 goes to position 2.
Argument order corrected. Type preserved. Await preserved.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-ast-analysis-refuses-to-rewrite&quot;&gt;When AST Analysis Refuses to Rewrite&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Not every call can be safely automated. FlagLint identifies these
and routes them to manual review instead of silently rewriting them:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic flag keys:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;flagKey&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;feature-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;featureName&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariation&lt;/span&gt;&lt;span&gt;(flagKey, ctx, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The key is not statically knowable. Automated rewrite could produce
incorrect OpenFeature client binding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Detail methods:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;boolVariationDetail&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;checkout-v2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, ctx, &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;boolVariationDetail&lt;/code&gt; returns metadata not directly equivalent in
OpenFeature. Requires a different migration pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bulk state calls:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; ldClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;allFlagsState&lt;/span&gt;&lt;span&gt;(ctx);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;No direct OpenFeature equivalent. Architecture decision required.&lt;/p&gt;
&lt;p&gt;For all of these, FlagLint reports the location and reason —
it never silently rewrites them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-ci-gate&quot;&gt;The CI Gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Generating diffs is only half the problem. Migration rot is the other half.&lt;/p&gt;
&lt;p&gt;After a phased migration, new engineers joining the codebase don’t
know the rule. They reach for &lt;code dir=&quot;auto&quot;&gt;ldClient&lt;/code&gt; because that’s what they know.
Six months later, you have new direct LD calls and the migration has
partially reversed.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;flaglint validate --no-direct-launchdarkly&lt;/code&gt; exits 1 if any direct
LaunchDarkly evaluation call appears outside the bootstrap file.&lt;/p&gt;
&lt;p&gt;Add it to your GitHub Actions workflow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Enforce OpenFeature boundary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;flaglint validate ./src --no-direct-launchdarkly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;always()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Any new &lt;code dir=&quot;auto&quot;&gt;ldClient.boolVariation()&lt;/code&gt; call fails the build. The boundary holds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it-now&quot;&gt;Try It Now&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Runs locally, no SDK key needed, nothing changes. Shows you every
direct LaunchDarkly SDK call in your codebase by file and line —
including dynamic keys, detail methods, and bulk state calls.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;flaglint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;migrate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dry-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Shows the before/after diff for every safely automatable call site.
Dynamic keys and detail methods are reported separately for manual review.&lt;/p&gt;
&lt;p&gt;FlagLint is free, open source, MIT licensed.&lt;/p&gt;
&lt;p&gt;→ &lt;a href=&quot;https://github.com/flaglint/flaglint&quot;&gt;GitHub&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://www.npmjs.com/package/flaglint&quot;&gt;npm&lt;/a&gt;&lt;br&gt;
→ &lt;a href=&quot;https://flaglint.dev/docs/quickstart&quot;&gt;Quickstart&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Related:&lt;/strong&gt; &lt;a href=&quot;https://flaglint.dev/blog/after-launchdarkly-outage-vendor-neutral-abstraction/&quot;&gt;After the LaunchDarkly Outage: Adding a Vendor-Neutral Abstraction Without a Full Migration →&lt;/a&gt;&lt;/p&gt;</content:encoded><category>launchdarkly</category><category>openfeature</category><category>migration</category><category>nodejs</category><category>devops</category></item></channel></rss>