Created: 2026-05-04 UTC Owner: operator / ops-agent Parent epic: #551 (in-console feature flag management) Runbook: docs/ops/feature-flags-runbook.md Design doc: docs/architecture/console-feature-flags.md sec 6 + 7 Follow-up cleanup issue: #1061
FLAG_* Heroku config vars were the original flip mechanism for feature flags. The
in-console flag management system (#551, #552, #553, #554) introduces a
DB-backed override layer that supersedes them.
Resolution order (post-#552):
1. console_feature_flags DB row — wins when FLAG_CONSOLE_FEATURE_FLAGS_DB=1 and row exists
2. FLAG_<UPPER_KEY> Heroku config var — bootstrap fallback
3. feature_flags.yaml default — final fallback
Once a flag is flipped via the console UI, a DB row is written for that (flag_key, env) pair.
The Heroku env var is then dormant for that pair — it still exists on Heroku but has no
effect. The env var remains a valid break-glass path only if FLAG_CONSOLE_FEATURE_FLAGS_DB=0
or if no DB row exists (see runbook §6).
Removal policy: remove a Heroku config var only after:
1. DB row confirmed present on both prod and staging.
2. >= 24h soak with DB row active and no regressions.
3. Audit log shows at least one console.flag.flip entry for each env.
Status values:
- active — env var is the live flip mechanism (DB layer not yet enabled or DB row absent)
- dormant-after-flip — DB row exists; env var has no effect for this flag+env pair
- can-remove — soaked >= 24h, both envs confirmed, ready for heroku config:unset
- removed — env var deleted from Heroku
Flags not listed here are managed via YAML default only and have no Heroku config var.
| Heroku env var | YAML key | Gating | Status | Notes |
|---|---|---|---|---|
FLAG_CONSOLE_BILLING |
billing_rbac |
billing permission checks + daily ops cap | active | was FLAG_BILLING_RBAC in YAML; code reads FLAG_CONSOLE_BILLING |
FLAG_CONSOLE_STATUS_API |
console_status_api |
/api/status/* endpoints on console |
active | |
FLAG_CONSOLE_DASHBOARD_HOME |
console_dashboard_home |
real status grid + alerts on /dashboard |
active | |
FLAG_CONSOLE_TELEMETRY_HARDENING |
console_telemetry_hardening |
degraded-vendor detection, stale annotations, poller health | active | |
FLAG_CONSOLE_CLAUDE_MENU |
console_claude_menu |
/ops/* routes + Ops nav dropdown |
active | superadmin only |
FLAG_CONSOLE_ROTATION_MODE_A |
console_rotation_mode_a |
/secrets rotation UI |
active | prerequisite for FLAG_CONSOLE_ROTATE_UI |
FLAG_CONSOLE_ROTATE_UI |
console_rotate_ui |
per-token Rotate button in vault panel | active | requires FLAG_CONSOLE_ROTATION_MODE_A |
FLAG_CONSOLE_ENV_GATE |
console_env_gate |
@require_env_match on mutating routes |
active | |
FLAG_CONSOLE_ENV_SWITCHER_BANNER |
console_env_switcher_banner |
env color banner (removed in #872 — banner always shown) | can-remove | banner is unconditional since #872; env var has no effect |
FLAG_CONSOLE_DEPLOY_UI |
console_deploy_ui |
Deploy button on site tiles + deploy API | active | |
FLAG_CONSOLE_DEPLOY_ASYNC |
(no yaml key) | async POST 202 path for deploys | active | internal sub-flag; not in YAML |
FLAG_CONSOLE_DEPLOY_XENV_READ |
(no yaml key) | cross-env deploy read endpoint | active | internal sub-flag; not in YAML |
FLAG_CONSOLE_DEPLOY_KV_WRITE |
(no yaml key) | KV write on deploy status transitions | active | internal sub-flag; not in YAML |
FLAG_CONSOLE_DEPLOY_CHIP |
console_deploy_chip |
cross-page in-flight deploy chip | active | |
FLAG_CONSOLE_FEATURE_FLAGS_DB |
console_feature_flags_db |
activates DB-backed flag reads (migration 0009) | active | meta-flag for the flag system itself |
FLAG_CONSOLE_FLAG_PROMOTIONS |
console_flag_promotions |
/flags/promotions + mark/promote/reject endpoints |
active | requires migration 0010 |
FLAG_CONSOLE_GITHUB_BUILD_STATUS |
console_github_build_status |
GH Actions build status in BUILD column | active | requires GITHUB_API_READONLY_TOKEN |
FLAG_CONSOLE_CF_PAGES_BUILD_STATUS |
console_cf_pages_build_status |
CF Pages build status in BUILD column | active | |
FLAG_CONSOLE_SENTRY_24H |
console_sentry_24h |
Sentry 24h error count on site tiles | active | requires SENTRY_API_TOKEN etc. |
FLAG_CONSOLE_SITE_LATENCY_CHART |
console_site_latency_chart |
48h latency sparkline on site detail | active | |
FLAG_CONSOLE_IP_GEO_WIDGET |
console_ip_geo_widget |
CF header-derived geo label in ticker | active | |
FLAG_CONSOLE_LOCATION_GEOCODE |
console_location_geocode |
auto-prompt + BigDataCloud reverse-geocode | active | client-side only |
FLAG_CONSOLE_TILE_DRAWER |
console_tile_drawer |
detail drawer DOM shell on dashboard | active | blocks sibling sub-cards #782-#787 |
FLAG_CONSOLE_HEROKU_DYNO_MONITOR |
console_heroku_dyno_monitor |
zero-dyno + no-slug detection on Heroku surfaces | active | |
FLAG_CONSOLE_NAV_V2 |
(no yaml key) | Security / Status / Issues top-level nav | active | YAML key absent; nav is now unconditional for op+ roles |
FLAG_CONSOLE_SECRET_ONBOARDING_AUTO |
(no yaml key) | secret onboarding auto-flip not_setup → auto | active | internal service-layer flag |
| Heroku env var | YAML key | Status | Notes |
|---|---|---|---|
FLAG_ENFORCE_CF_ORIGIN |
enforce_cf_origin |
active | CF origin guard; flip after staging soak |
FLAG_BILLING_RBAC |
billing_rbac |
active | YAML key; code reads FLAG_CONSOLE_BILLING |
FLAG_STATUS_PUBLIC_ENDPOINT |
status_public_endpoint |
active | CF Worker handles reads |
FLAG_STATUS_3P_POLLER |
status_3p_poller |
active | requires STATUS_WORKER_URL + STATUS_INTERNAL_WRITE_TOKEN |
FLAG_STATUS_D1_WORKER |
status_d1_worker |
active | sentinel; flip after D1 provisioned |
FLAG_STATUS_FREESCOUT_FIELDS |
status_freescout_fields |
active | prerequisite for #607 |
FLAG_SUPPORT_S3_ATTACHMENTS |
support_s3_attachments |
active | requires terraform apply |
FLAG_SENTRY_BACKEND |
sentry_backend |
active | |
FLAG_SECURITY_HEADERS |
security_headers |
active | default ON in YAML; kill-switch only |
FLAG_SECURITY_HEADERS_CSP |
security_headers_csp |
active | default OFF; validate CSP before enabling |
FLAG_ANTLERS_OBFUSCATE_MODE |
antlers_obfuscate_mode |
active | frontend render-layer only |
FLAG_PASSKEY_SIGNUP_UI |
passkey_signup_ui |
active | requires FLAG_WEBAUTHN_REGISTRATION |
FLAG_PASSKEY_LOGIN_UI |
passkey_login_ui |
active | requires passkey_signup_ui soaked |
FLAG_ROUTE_GUARD |
route_guard |
active | requires passkey_login_ui |
FLAG_EMAIL_VERIFICATION_UI |
email_verification_ui |
active | requires #112 backend |
FLAG_RAXX_APP_SHELL |
raxx_app_shell |
active | Raxx brand chrome on authenticated routes |
FLAG_RATE_LIMIT_STORAGE_REDIS |
rate_limit_storage_redis |
active | requires REDIS_URL |
FLAG_BOOTSTRAP_TOKEN_VALIDATION |
bootstrap_token_validation |
active | informational; requires BOOTSTRAP_TOKEN_SIGNING_KEY |
FLAG_ENABLE_OPTIONS_BACKTEST |
(ENABLE_OPTIONS_BACKTEST in yaml) | active | licensing gate; keep until #244 resolved |
Issue #1061 tracks the actual
heroku config:unset step. That issue is intentionally separate from this doc so the unset
step is never automated silently — operators must confirm via the audit log that the DB row
is live before removing the env var safety net.
Do not remove any Heroku config var until:
1. The DB row for that (flag_key, env) pair is confirmed present on both prod and staging.
2. The flag has soaked for >= 24h with no regressions.
3. The audit log shows at least one console.flag.flip entry per env.
4. The status in this table has been updated to can-remove.
When an operator flips a flag via the console UI for the first time on a given env:
sql
SELECT flag_key, env, value, updated_at FROM console_feature_flags
WHERE flag_key = '<key>' AND env = '<env>';dormant-after-flip.can-remove.heroku config:unset on both apps: update to removed and record the date.There is no hard deadline for removing dormant Heroku config vars. The recommended window is within 30 days of GA sign-off for any flag where the DB row is confirmed live on both envs.
The FLAG_CONSOLE_FLAG_MGMT env var (the rollout gate for the flag management feature itself)
is the only var with a firm removal target: it should be removed as part of GA sign-off for #551,
since the feature self-gate is not needed post-GA. See runbook §9 (GA sign-off checklist).