Issue: #1061
Prereq cleared: PR #1712 (§9 GA gate — all 46 os.environ.get("FLAG_*") callsites migrated to flags.is_on() DB-backed resolution)
Deprecation table: docs/ops/feature-flags-heroku-deprecation.md
Full ops runbook: docs/ops/feature-flags-runbook.md
Apps in scope: raxx-console-prod, raxx-console-staging
Owner: operator
FLAG_CONSOLE_* Heroku config vars were the original flip mechanism for feature flags before the
DB-backed flag layer shipped. With FLAG_CONSOLE_FEATURE_FLAGS_DB=1 set on both apps and all code
callsites migrated to flags.is_on() (PR #1712), the Heroku env vars are dormant for any flag
that has a DB row. They still exist on Heroku but have no effect while the DB row is present.
This runbook gives the operator a pre-flight checklist, the exact heroku config:unset commands,
and a post-unset verification checklist.
Run this for each flag you intend to unset. Connect to the console DB (via heroku pg:psql):
heroku pg:psql --app raxx-console-prod
heroku pg:psql --app raxx-console-staging
Query to confirm DB rows exist:
-- Confirm all FLAG_CONSOLE_* flags have DB rows on each env
SELECT flag_key, env, value, updated_at
FROM console_feature_flags
WHERE flag_key LIKE 'console_%'
ORDER BY flag_key, env;
For each flag key listed in Part 2 below, you need a row for both env='prod' and
env='staging' before removing that flag's Heroku config var from the corresponding app.
Query to confirm audit trail (at least one flip per env per flag):
SELECT target_id AS flag_key,
payload->>'env' AS env,
COUNT(*) AS flip_count,
MAX(created_at) AS last_flip
FROM console_audit_log
WHERE action = 'console.flag.flip'
GROUP BY target_id, payload->>'env'
ORDER BY target_id, env;
Do not proceed with unset for any flag that has no DB row or no audit entry.
These flags have no remaining code reference in console/app/:
| Heroku env var | Reason safe to remove |
|---|---|
FLAG_CONSOLE_ENV_SWITCHER_BANNER |
Banner is unconditional since PR #872. No is_on() call, no os.environ.get call. The Heroku var is purely inert. |
All of the following are resolved exclusively via flags.is_on() after PR #1712. Their Heroku
env vars are dormant once the DB row exists. Confirm DB row + audit entry for each env before
running the unset.
| Heroku env var | YAML / flag key |
|---|---|
FLAG_CONSOLE_ADMINS_ONLINE |
console_admins_online |
FLAG_CONSOLE_ALERTS_AUTO_TICKET |
console_alerts_auto_ticket |
FLAG_CONSOLE_ALERTS_BELL |
console_alerts_bell |
FLAG_CONSOLE_ALERTS_DRAWER_MULTISOURCE |
console_alerts_drawer_multisource |
FLAG_CONSOLE_BILLING |
console_billing |
FLAG_CONSOLE_CF_PAGES_BUILD_STATUS |
console_cf_pages_build_status |
FLAG_CONSOLE_CLAUDE_MENU |
console_claude_menu |
FLAG_CONSOLE_CUSTOMER_ADMIN |
console_customer_admin |
FLAG_CONSOLE_CUSTOMER_AUDIT_VIEWER |
console_customer_audit_viewer |
FLAG_CONSOLE_DASHBOARD_HOME |
console_dashboard_home |
FLAG_CONSOLE_DEPLOY_ASYNC |
console_deploy_async |
FLAG_CONSOLE_DEPLOY_AUDIT_INGEST |
console_deploy_audit_ingest |
FLAG_CONSOLE_DEPLOY_CHIP |
console_deploy_chip |
FLAG_CONSOLE_DEPLOY_KV_WRITE |
console_deploy_kv_write |
FLAG_CONSOLE_DEPLOY_OBSERVABILITY |
console_deploy_observability |
FLAG_CONSOLE_DEPLOY_RESUME |
console_deploy_resume |
FLAG_CONSOLE_DEPLOY_UI |
console_deploy_ui |
FLAG_CONSOLE_DEPLOY_VIEW_LOGS |
console_deploy_view_logs |
FLAG_CONSOLE_DEPLOY_XENV_READ |
console_deploy_xenv_read |
FLAG_CONSOLE_DRAWER_ACTIONS |
console_drawer_actions |
FLAG_CONSOLE_DRIFT_WIDGET |
console_drift_widget |
FLAG_CONSOLE_ENV_GATE |
console_env_gate |
FLAG_CONSOLE_FLAG_DRIFT_WIDGET |
console_flag_drift_widget |
FLAG_CONSOLE_FLAG_PROMOTION_WATCHDOG |
console_flag_promotion_watchdog |
FLAG_CONSOLE_FLAG_PROMOTIONS |
console_flag_promotions |
FLAG_CONSOLE_FLAGS_BY_SURFACE |
console_flags_by_surface |
FLAG_CONSOLE_GITHUB_BUILD_STATUS |
console_github_build_status |
FLAG_CONSOLE_GOOGLE_OAUTH_FALLBACK |
console_google_oauth_fallback |
FLAG_CONSOLE_HEROKU_DYNO_MONITOR |
console_heroku_dyno_monitor |
FLAG_CONSOLE_HEROKU_LOG_DRAIN_ALERTING |
console_heroku_log_drain_alerting |
FLAG_CONSOLE_INVESTIGATE_FROM_STATUS |
console_investigate_from_status |
FLAG_CONSOLE_IP_GEO_WIDGET |
console_ip_geo_widget |
FLAG_CONSOLE_LOCATION_GEOCODE |
console_location_geocode |
FLAG_CONSOLE_MINIMIZABLE_MODAL |
console_minimizable_modal |
FLAG_CONSOLE_NAV_V2 |
console_nav_v2 |
FLAG_CONSOLE_POST_PROMOTION_HEALTH |
console_post_promotion_health |
FLAG_CONSOLE_PROMOTION_EXPIRY_SCHEDULER |
console_promotion_expiry_scheduler |
FLAG_CONSOLE_PROMOTIONS_READY_BADGE |
console_promotions_ready_badge |
FLAG_CONSOLE_RECENTS_AUTOCOLLAPSE |
console_recents_autocollapse |
FLAG_CONSOLE_REPLAY_RBAC_GATE |
console_replay_rbac_gate |
FLAG_CONSOLE_REPLAY_SPLIT_VIEW |
console_replay_split_view |
FLAG_CONSOLE_REPLAY_TIMELINE |
console_replay_timeline |
FLAG_CONSOLE_ROLLBACK_SIGNAL_LISTENER |
console_rollback_signal_listener |
FLAG_CONSOLE_ROTATE_UI |
console_rotate_ui |
FLAG_CONSOLE_ROTATION_MODE_A |
console_rotation_mode_a |
FLAG_CONSOLE_SECRET_ONBOARDING_AUTO |
console_secret_onboarding_auto |
FLAG_CONSOLE_SENTRY_24H |
console_sentry_24h |
FLAG_CONSOLE_SITE_LATENCY_CHART |
console_site_latency_chart |
FLAG_CONSOLE_STATUS_API |
console_status_api |
FLAG_CONSOLE_STATUS_CONFIG |
console_status_config |
FLAG_CONSOLE_TELEMETRY_HARDENING |
console_telemetry_hardening |
FLAG_CONSOLE_TILE_DRAWER |
console_tile_drawer |
FLAG_CONSOLE_TILE_HOVER_SPARKLINE |
console_tile_hover_sparkline |
FLAG_CONSOLE_VAULT_ENV_AWARE |
console_vault_env_aware |
FLAG_CONSOLE_VERSION_MANAGER |
console_version_manager |
| Heroku env var | Reason to keep |
|---|---|
FLAG_CONSOLE_FEATURE_FLAGS_DB |
Bootstrap guard for the flag system itself. Migrating this would create a circular dependency — the DB layer uses this guard to decide whether to use the DB. Stays as env-var-only permanently. See PR #1712 comment. |
FLAG_CONSOLE_VELVET_INTEGRATION |
Still resolved via os.environ.get in console/app/services/velvet_client.py line 157. Not yet migrated to flags.is_on(). Remove only after that migration ships. |
FLAG_CONSOLE_P5_DRAWER_API |
Not present in codebase grep. Verify it exists on Heroku before deciding. |
FLAG_CONSOLE_DEPLOY_PULSE |
Referenced in console/app/__init__.py via flags.is_on("console_deploy_pulse") — migrated by #1712, but verify DB row before removal. |
All heroku config:unset calls must silence stdout to avoid echoing the full config including
adjacent secrets (>/dev/null 2>&1).
Use the script at scripts/console/cleanup-dormant-flags.sh for the full automated listing pass.
For manual targeted removal:
# Unset a single flag on both apps (replace FLAG_CONSOLE_EXAMPLE):
heroku config:unset FLAG_CONSOLE_EXAMPLE --app raxx-console-prod >/dev/null 2>&1
heroku config:unset FLAG_CONSOLE_EXAMPLE --app raxx-console-staging >/dev/null 2>&1
To remove all flags from Part 2a + 2b in one pass (run only after Part 1 confirms DB rows):
# --- staging first ---
heroku config:unset \
FLAG_CONSOLE_ENV_SWITCHER_BANNER \
FLAG_CONSOLE_ADMINS_ONLINE \
FLAG_CONSOLE_ALERTS_AUTO_TICKET \
FLAG_CONSOLE_ALERTS_BELL \
FLAG_CONSOLE_ALERTS_DRAWER_MULTISOURCE \
FLAG_CONSOLE_BILLING \
FLAG_CONSOLE_CF_PAGES_BUILD_STATUS \
FLAG_CONSOLE_CLAUDE_MENU \
FLAG_CONSOLE_CUSTOMER_ADMIN \
FLAG_CONSOLE_CUSTOMER_AUDIT_VIEWER \
FLAG_CONSOLE_DASHBOARD_HOME \
FLAG_CONSOLE_DEPLOY_ASYNC \
FLAG_CONSOLE_DEPLOY_AUDIT_INGEST \
FLAG_CONSOLE_DEPLOY_CHIP \
FLAG_CONSOLE_DEPLOY_KV_WRITE \
FLAG_CONSOLE_DEPLOY_OBSERVABILITY \
FLAG_CONSOLE_DEPLOY_RESUME \
FLAG_CONSOLE_DEPLOY_UI \
FLAG_CONSOLE_DEPLOY_VIEW_LOGS \
FLAG_CONSOLE_DEPLOY_XENV_READ \
FLAG_CONSOLE_DRAWER_ACTIONS \
FLAG_CONSOLE_DRIFT_WIDGET \
FLAG_CONSOLE_ENV_GATE \
FLAG_CONSOLE_FLAG_DRIFT_WIDGET \
FLAG_CONSOLE_FLAG_PROMOTION_WATCHDOG \
FLAG_CONSOLE_FLAG_PROMOTIONS \
FLAG_CONSOLE_FLAGS_BY_SURFACE \
FLAG_CONSOLE_GITHUB_BUILD_STATUS \
FLAG_CONSOLE_GOOGLE_OAUTH_FALLBACK \
FLAG_CONSOLE_HEROKU_DYNO_MONITOR \
FLAG_CONSOLE_HEROKU_LOG_DRAIN_ALERTING \
FLAG_CONSOLE_INVESTIGATE_FROM_STATUS \
FLAG_CONSOLE_IP_GEO_WIDGET \
FLAG_CONSOLE_LOCATION_GEOCODE \
FLAG_CONSOLE_MINIMIZABLE_MODAL \
FLAG_CONSOLE_NAV_V2 \
FLAG_CONSOLE_POST_PROMOTION_HEALTH \
FLAG_CONSOLE_PROMOTION_EXPIRY_SCHEDULER \
FLAG_CONSOLE_PROMOTIONS_READY_BADGE \
FLAG_CONSOLE_RECENTS_AUTOCOLLAPSE \
FLAG_CONSOLE_REPLAY_RBAC_GATE \
FLAG_CONSOLE_REPLAY_SPLIT_VIEW \
FLAG_CONSOLE_REPLAY_TIMELINE \
FLAG_CONSOLE_ROLLBACK_SIGNAL_LISTENER \
FLAG_CONSOLE_ROTATE_UI \
FLAG_CONSOLE_ROTATION_MODE_A \
FLAG_CONSOLE_SECRET_ONBOARDING_AUTO \
FLAG_CONSOLE_SENTRY_24H \
FLAG_CONSOLE_SITE_LATENCY_CHART \
FLAG_CONSOLE_STATUS_API \
FLAG_CONSOLE_STATUS_CONFIG \
FLAG_CONSOLE_TELEMETRY_HARDENING \
FLAG_CONSOLE_TILE_DRAWER \
FLAG_CONSOLE_TILE_HOVER_SPARKLINE \
FLAG_CONSOLE_VAULT_ENV_AWARE \
FLAG_CONSOLE_VERSION_MANAGER \
--app raxx-console-staging >/dev/null 2>&1
# Wait 5 minutes; confirm staging healthy before proceeding to prod
# (see Part 4 verification checklist)
# --- prod after staging soak ---
heroku config:unset \
FLAG_CONSOLE_ENV_SWITCHER_BANNER \
FLAG_CONSOLE_ADMINS_ONLINE \
FLAG_CONSOLE_ALERTS_AUTO_TICKET \
FLAG_CONSOLE_ALERTS_BELL \
FLAG_CONSOLE_ALERTS_DRAWER_MULTISOURCE \
FLAG_CONSOLE_BILLING \
FLAG_CONSOLE_CF_PAGES_BUILD_STATUS \
FLAG_CONSOLE_CLAUDE_MENU \
FLAG_CONSOLE_CUSTOMER_ADMIN \
FLAG_CONSOLE_CUSTOMER_AUDIT_VIEWER \
FLAG_CONSOLE_DASHBOARD_HOME \
FLAG_CONSOLE_DEPLOY_ASYNC \
FLAG_CONSOLE_DEPLOY_AUDIT_INGEST \
FLAG_CONSOLE_DEPLOY_CHIP \
FLAG_CONSOLE_DEPLOY_KV_WRITE \
FLAG_CONSOLE_DEPLOY_OBSERVABILITY \
FLAG_CONSOLE_DEPLOY_RESUME \
FLAG_CONSOLE_DEPLOY_UI \
FLAG_CONSOLE_DEPLOY_VIEW_LOGS \
FLAG_CONSOLE_DEPLOY_XENV_READ \
FLAG_CONSOLE_DRAWER_ACTIONS \
FLAG_CONSOLE_DRIFT_WIDGET \
FLAG_CONSOLE_ENV_GATE \
FLAG_CONSOLE_FLAG_DRIFT_WIDGET \
FLAG_CONSOLE_FLAG_PROMOTION_WATCHDOG \
FLAG_CONSOLE_FLAG_PROMOTIONS \
FLAG_CONSOLE_FLAGS_BY_SURFACE \
FLAG_CONSOLE_GITHUB_BUILD_STATUS \
FLAG_CONSOLE_GOOGLE_OAUTH_FALLBACK \
FLAG_CONSOLE_HEROKU_DYNO_MONITOR \
FLAG_CONSOLE_HEROKU_LOG_DRAIN_ALERTING \
FLAG_CONSOLE_INVESTIGATE_FROM_STATUS \
FLAG_CONSOLE_IP_GEO_WIDGET \
FLAG_CONSOLE_LOCATION_GEOCODE \
FLAG_CONSOLE_MINIMIZABLE_MODAL \
FLAG_CONSOLE_NAV_V2 \
FLAG_CONSOLE_POST_PROMOTION_HEALTH \
FLAG_CONSOLE_PROMOTION_EXPIRY_SCHEDULER \
FLAG_CONSOLE_PROMOTIONS_READY_BADGE \
FLAG_CONSOLE_RECENTS_AUTOCOLLAPSE \
FLAG_CONSOLE_REPLAY_RBAC_GATE \
FLAG_CONSOLE_REPLAY_SPLIT_VIEW \
FLAG_CONSOLE_REPLAY_TIMELINE \
FLAG_CONSOLE_ROLLBACK_SIGNAL_LISTENER \
FLAG_CONSOLE_ROTATE_UI \
FLAG_CONSOLE_ROTATION_MODE_A \
FLAG_CONSOLE_SECRET_ONBOARDING_AUTO \
FLAG_CONSOLE_SENTRY_24H \
FLAG_CONSOLE_SITE_LATENCY_CHART \
FLAG_CONSOLE_STATUS_API \
FLAG_CONSOLE_STATUS_CONFIG \
FLAG_CONSOLE_TELEMETRY_HARDENING \
FLAG_CONSOLE_TILE_DRAWER \
FLAG_CONSOLE_TILE_HOVER_SPARKLINE \
FLAG_CONSOLE_VAULT_ENV_AWARE \
FLAG_CONSOLE_VERSION_MANAGER \
--app raxx-console-prod >/dev/null 2>&1
Run these checks within 5 minutes of the unset on each app.
# Should print nothing (or "No such config var") for each var you unset:
heroku config --app raxx-console-staging 2>/dev/null | grep "^FLAG_CONSOLE_" || echo "No FLAG_CONSOLE_ vars found"
heroku config --app raxx-console-prod 2>/dev/null | grep "^FLAG_CONSOLE_" || echo "No FLAG_CONSOLE_ vars found"
Expected: only FLAG_CONSOLE_FEATURE_FLAGS_DB and FLAG_CONSOLE_VELVET_INTEGRATION remain.
Everything else should be absent.
# Flags page should load without errors (requires session cookie):
curl -sI https://console.raxx.app/console/flags | grep "HTTP/"
# Expected: HTTP/2 200 (or 302 redirect to login if session not set)
curl -sI https://console-staging.raxx.app/console/flags | grep "HTTP/"
# Expected: same
Connect to the DB and verify the rows are still present and unchanged after the unset:
-- Run on both prod and staging after config:unset
SELECT flag_key, env, value, updated_at
FROM console_feature_flags
WHERE flag_key IN (
'console_billing',
'console_dashboard_home',
'console_flag_promotions',
'console_env_gate',
'console_deploy_ui',
'console_feature_flags_db'
)
ORDER BY flag_key, env;
Expected: rows present and value unchanged. If a row is absent for a flag that was expected
to be on, the flag resolves to its YAML default. Check feature_flags.yaml to see what that
default is.
heroku ps --app raxx-console-staging
heroku ps --app raxx-console-prod
# Expected: web.1: up (may take 30-60s post-unset to show up)
If dynos are cycling or in error state, check:
heroku logs --tail --app raxx-console-prod
Load the console dashboard on both staging and prod. Confirm:
- Dashboard tiles render (no blank page, no 500)
- Nav items present (Security / Status / Issues)
- Flag management page (/console/flags) accessible and shows current flag states
After unset and verification:
docs/ops/feature-flags-heroku-deprecation.md — change each removed var's status to
removed and add the removal date.FLAG_CONSOLE_VELVET_INTEGRATION cleanup is tracked separately (needs flags.is_on()
migration in velvet_client.py first).If anything breaks after the unset, the env vars can be restored immediately:
heroku config:set FLAG_CONSOLE_<KEY>=1 --app raxx-console-prod >/dev/null 2>&1
heroku config:set FLAG_CONSOLE_<KEY>=1 --app raxx-console-staging >/dev/null 2>&1
The DB row still wins over the env var while FLAG_CONSOLE_FEATURE_FLAGS_DB=1, so restoring the
env var does not change flag behavior for any flag that has a DB row. To force a specific flag
value when a DB row exists, update the DB row directly (see docs/ops/feature-flags-runbook.md §6).