Deprecate an env variable
This is the reverse of the add-env-var skill. Read .agents/rules/env-vars.md
first for background: how runtime delivery works, how the config object is
structured, the three value types, and where validation lives.
How removal is enforced
Two startup checks make a removed variable fail loudly, so you rarely need to write a custom guard:
- The validator schemas use
.noUnknown(true)— anyNEXT_PUBLIC_*key not declared in a schema fails validation. checkPlaceholdersCongruity(indeploy/tools/envs-validator/index.ts) throws if an env has no build-time placeholder. Placeholders come from.env.registry, whichcollect_envs.shgenerates by scanningdocs/ENVS.md.
Net effect: a variable removed from both docs/ENVS.md and the schema will
fail container startup if an operator still sets it. This is the hard stop
behind "immediate" removal, and the reason a grace period must keep the
variable in both places until the final removal.
Step 0 — Find the current state, then pick the path
A variable already mid-grace-period must not be treated as a fresh
deprecation. First check whether it is already deprecated from an earlier
release: grep deploy/tools/envs-validator/index.ts for a
printDeprecationWarning / checkDeprecatedEnvs entry naming it, and check its
docs/ENVS.md row for a "Deprecated…" annotation.
Already in a grace period — a request to "remove it" is the removal phase. Go straight to Branch B → Phase 2; do not ask the user which branch.
Not yet deprecated — run the deployment-values check below; it names the branch. Put that recommendation to the user and have them confirm it — do not pick a branch silently:
Immediate removal — the variable is deleted in this release. Operators who still pass it get a startup failure. Right when the feature is gone entirely, or the value now comes from elsewhere (e.g. the API) and no migration by the operator is expected. Precedents:
NEXT_PUBLIC_HAS_MUD_FRAMEWORK,NEXT_PUBLIC_SAVE_ON_GAS_ENABLED, the Sentry variables.Grace period — the variable keeps working for one or more releases with a runtime deprecation warning, then is removed in a later release. Right when operators need time to migrate, typically because the variable was renamed or replaced by a new one. Precedent:
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY→NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY(#2384).
Also settle: is there a replacement variable? This changes the grace-period
recipe and the Comment you write in the docs.
Where is the variable actually set? (deployment-values check)
What is safe is decided by where hosted instances get the variable from, not by
how dead the feature looks — so read the DevOps config repo
blockscout/deployment-values rather than reasoning about it (private repo;
gh must be authenticated for it — see the check-github-cli skill):
gh api "search/code?q=repo:blockscout/deployment-values+NEXT_PUBLIC_FOO" --jq '.total_count, (.items[].path)'
Open every match — the count alone decides nothing:
gh api "repos/blockscout/deployment-values/contents/<path>" --jq '.content' | base64 -d
Read where it is set, and to what. The sampled values bound how much of
the variable's behaviour is really in use: NEXT_PUBLIC_ACCOUNT_API_KEYS_BUTTON
was only ever set to 'false', which is what made dropping its URL-string mode
safe.
- Per-instance files only (
common/values/blockscout/<instance>/values*.yaml*) → Branch A. DevOps drops the value instance by instance as they roll the release out. - The common template (
common/values/blockscout/values.yaml.gotmpl) → Branch B. Immediate removal breaks every hosted instance's startup until the DevOps cleanup merges. - No matches → Branch A, nothing to coordinate at all.
Branch A — Immediate removal
Do all of the following in one PR. Requires the Step 0 check to have cleared it: set only in per-instance files, or nowhere.
A1 — Move the docs row
- Delete the variable's row (and its section/heading if it was the only row)
from
docs/ENVS.md. - Append a row to
docs/DEPRECATED_ENVS.md. The Description must be the variable's original functional description — if a grace period appended a deprecation note (Deprecated — use NEXT_PUBLIC_FOO instead.), strip it back to the original. Set Deprecated in version toupcoming(the release process replaces it), and put the deprecation reason or replacement only in the Comment column — e.g.Feature is deprecated.,Replaced with NEXT_PUBLIC_FOO, orRemoved; configuration done on the API side.
A2 — Remove the validator rule
- Delete the rule from wherever it lives: inline in
schema.ts, inschema_multichain.ts, or in a feature sub-schema underschemas/features/<name>.ts. Remove it from both schemas / sub-schemas if it applied in both modes. - Remove the variable from every test preset under
deploy/tools/envs-validator/test/(.env.base,.env.alt,.env.multichain, scenario presets). If it was a JSON-config-URL variable, also delete its example payload undertest/assets/configs/and its entry in theenvsWithJsonConfigarray inindex.ts.
A3 — Remove the app code
- Remove the sub-config that reads it (the
getEnvValue('NEXT_PUBLIC_…')/parseEnvJson/getExternalAssetFilePathcall) and every consumer. - If a whole feature is being removed: delete the feature folder
(
config.ts, components,mocks/,*.pw.tsxand their committed screenshots) and remove its export from the aggregatorsrc/config/features.ts.
A4 — Remove downstream references (only those that existed)
Check each and clean up if the variable/feature touched it:
- CSP allowance under
src/server/csp/policies/. ASSETS_ENVSarray indeploy/scripts/download_assets.sh(asset-URL vars).deploy/tools/sitemap-generator/next-sitemap.config.js.deploy/tools/llms-txt-generator/generators.public/icons/name.d.ts(if a feature-only icon is gone)..agents/GLOSSARY.md.
A5 — Stop the demo deploy from re-supplying it
Add the removed variable to the deprecatedEnvs array in
tools/dev-server/envs-rules.json. The local dev server and the demo (review)
deploy fetch a live instance's config over HTTP, and that instance still
serves the old variable; deprecatedEnvs drops it before validation. Without
this, the demo deploy fails on a variable that no longer exists in the schema.
See tools/dev-server/CONTEXT.md § "Dropped envs" for the full why.
A6 — (Optional) friendlier error for a replaced variable
The .noUnknown + congruity checks already fail startup with a generic
message. If the variable was replaced and you want operators to see a
clear "use X instead" message, add a guard to checkDeprecatedEnvs() in
deploy/tools/envs-validator/index.ts that throws with that message.
Branch B — Grace period (two phases)
Phase 1 — Deprecation release (this PR)
Docs — keep the row in
docs/ENVS.md(it still functions). Append a deprecation note to its Description, leaving the original text intact (e.g.… original description. Deprecated — use NEXT_PUBLIC_FOO instead.) — Phase 2 strips this note when it moves the row. Do not touchdocs/DEPRECATED_ENVS.mdyet. If there is a replacement variable, add it now following theadd-env-varskill.App code — keep reading the variable. For a replacement, read the new one with a fallback to the old at the call site:
getEnvValue('NEXT_PUBLIC_FOO') || getEnvValue('NEXT_PUBLIC_OLD').Inert variant — when the feature is already gone there is nothing left to read, and the grace period exists only so the common template can keep setting the variable: stop reading it now, and let the schema, the docs row and the warning carry the rest of Phase 1. Phase 2 is then a pure cleanup.
Validator schema — keep the old variable accepted in
schema.ts/schema_multichain.ts. If it was Required, make it optional now (the new variable carries the requirement). Keep its test-preset entries valid.Validator messaging (
deploy/tools/envs-validator/index.ts):- Always add a non-fatal warning in
printDeprecationWarning()when the old variable is present (the❗❗❗ … will be removed in the next release …block). - If there is a replacement: also add a guard in
checkDeprecatedEnvs()that throws when the old variable is set without the new one — forces operators onto the new name while still accepting both. (This is theNEXT_PUBLIC_RE_CAPTCHA_*pattern from #2384.) - If there is no replacement: warn only; do not throw.
- Always add a non-fatal warning in
Phase 2 — Removal release (a later PR)
Run the entire Branch A checklist for the old variable, plus: remove
the warning block you added to printDeprecationWarning() and the guard in
checkDeprecatedEnvs() in Phase 1.
Both branches — finishing up
- Version columns stay
upcoming; theprepare-releaseskill rewrites them to the shipping tag. - Label the PR
ENVsso the change is picked up into the "Changes in ENV variables" section of the release notes. - Name every removed variable in the PR description, and say the removal is
breaking.
prepare-releasereadsENVs-labelled PR bodies to build both the release notes and the roll-up request to DevOps, so this description is how DevOps learns what to drop from deployment-values, and for which release. It is the only place that list needs to live. - Run the validator suite and check the negative path:
Each preset should end withpnpm --filter envs-validator test👍 All good!. For a grace-period guard, temporarily set the old variable without the new one in a preset and confirm startup fails as intended, then revert. Seedeploy/tools/envs-validator/CONTEXT.mdfor details.