refactor(config): give feature flags provenance, typed ids, and an honest boot log - #551
Open
RonenMars wants to merge 1 commit into
Open
refactor(config): give feature flags provenance, typed ids, and an honest boot log#551RonenMars wants to merge 1 commit into
RonenMars wants to merge 1 commit into
Conversation
…nest boot log
The resolver collapsed env → CLI → yaml → default into a bare boolean with a `??` chain, so nothing could answer "why is this flag on?".
It now walks the rungs as an ordered list and returns `{ values, sources }`, both total, where `sources[id]` names the rung that decided each flag.
`GET /api/config/feature-flags` gains an additive `sources` field so that question is answerable over HTTP rather than by reading the environment, the argv and server.yaml by hand.
The boot log stated the opposite of the truth for a flag defaulting ON.
It printed the ids differing from their defaults under the heading "Feature flags active", so disabling `sessionRehydration` listed it as active, and it went silent on a stock boot.
It now prints every flag as `id=value(source)` under `event: "config.feature_flags"`.
`FeatureFlagId` is derived from the registry instead of `Record<string, boolean>`, so a typo'd consumer is a compile error rather than `undefined` read as falsy.
That found a real one: `cli/prod.ts` reads the resolver's result directly and was never covered by the src-only greps.
The legacy `codexSystemPromptEnabled` override becomes a real rung inside the resolver rather than a mutation applied to the finished map, which is what CLAUDE.md already documented.
Adds a test that scans src/ and cli/ for every registry id, so a flag cannot be declared, validated, persisted and served while gating nothing.
This was referenced Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #545 — its branch is the base, so this diff shows only the refactor.
Merge #545 first; GitHub will retarget this to
mainautomatically.The bug this starts from
The boot log stated the opposite of the truth for any flag whose default is ON.
nonDefaultFeatureFlagsreturns ids that differ from their default, under a heading that says active.sessionRehydrationdefaults on, so disabling it printedFeature flags active: sessionRehydration— naming a flag as active at the moment it was turned off.Three of the four flags default off, which is why the message read correctly often enough to survive.
It also logged names only, and went silent on a stock boot, so the log could never answer "what was this process actually running with".
What changed
Provenance. The resolver walked
env ?? cli ?? yaml ?? defaultand discarded which rung won.It now returns
{ values, sources }, both total, wheresources[id]is"override" | "env" | "cli" | "yaml" | "default".GET /api/config/feature-flagscarriessourcesas an additive field, so "why is this on?" is answerable over HTTP instead of by hand-reading the environment, the argv and server.yaml.Boot log. Every flag, every boot, as
id=value(source)underevent: "config.feature_flags".Typed ids.
FeatureFlagIdis derived from the registry, replacingRecord<string, boolean>.A typo'd consumer was previously
undefinedread as falsy — silently disabling the feature it was meant to gate.It is now a compile error, and it found a real consumer I had missed:
cli/prod.tsreads the resolver's result directly, outside everysrc/-only grep.The legacy override is a real rung.
codexSystemPromptEnabledwas applied by mutating the resolved map afterwards, so the resolver was not actually the single source of truth and an override was invisible to any reporting.CLAUDE.md already documented it as precedence rung 1; the code now matches.
A flag that gates nothing now fails a test.
feature-flags.test.tsscanssrc/andcli/for each registry id.Without it a flag can be declared, validated, persisted to server.yaml and served over HTTP while affecting nothing, with every layer reporting success — the same bug that shipped once in
claude-flags.Not done, deliberately
No remote flag service.
Resolution stays boot-time only:
ptyHostdecides whether live PTYs run in a separate host process, and re-evaluating that mid-flight would strand sessions across two regimes with no migration path.Verified state
npm run lintclean (tsc + biome).119/119 across the five suites that touch flags:
feature-flags,live-activity-flag,session-rehydration,prod-commands,session-settings.The new unread-flag guard was given a positive control rather than trusted: adding a
temporaryProbeFlagto the registry failed it withexpected [ 'temporaryProbeFlag' ] to deeply equal [], and it was removed again.Note for reviewers:
tsconfig.jsonexcludes__tests__, sonpm run linttype-checks neither this PR's test changes nor any others.A test passing the whole resolution object where a values map was expected got through lint and surfaced only when the suite ran.