Repository navigation
feat(cli)!: resolve config values as flags > env > config.toml > defaults - #7074
Conversation
Add the registry, pure picker, flag bindings, project env loader and CliConfigValues service that later changes migrate the readers onto. No command reads through them yet. @supabase/config/internal gains the parse + merge and decode + validate stages loadCliConfig is built from.
Decode config once per load and read values from that result, keep package errors visible by tag, fail on secret decrypt errors, scope linked-project credentials by link file, and close the precedence bypasses (rawDocument, registry-name lookups, duplicate flag assignments). The env loader moves to shared/config and the config package gains an explicit validateRemotes option plus a parse/merge split for document-derived env discovery.
Replaces resolveDbPassword and the explicit-ref env rule with the loader's linked-target credential scoping. Adds a shared DbPasswordFlagsError rejecting --password with --db-url or --local, makes gen types --local read [db].password, and routes bootstrap config writes through writeThrough.
Seed flags (--include-seed, --no-seed, --sql-paths), --use-pg-delta and --password on db push/reset/diff/pull are built from config keys and the effective values come from the config snapshot. Seeding into a target that matched a [remotes.*] block now asks first (--yes confirms; non-interactive runs fail before any write). The local Docker project id for pg-delta and the local-db probe comes from the snapshot.
readDbToml is now a projection of a CliConfigValues snapshot: flag, shell, project .env, config (matched remote over base), then default. The per-key remote overlay is retired and remoteOverrideKeys is always empty.
…Values start, stack-config, status gates, storage credentials and the services version lookup now take their effective values from the CliConfigValues snapshot instead of re-deriving shell/dotenv/remote precedence by hand. Commands that reach these paths provide the snapshot layer.
Local Docker readers (local-config-values, bootstrap, container inputs, project context, functions config) take their effective values from the CliConfigValues snapshot instead of hand-written env/remote precedence. The Invalid*EnvOverrideError classes collapse into CliConfigValueError, projectEnvValues holds only supabase/.env* file values, and the Docker project id is the sanitized workdir name unless project_id or SUPABASE_PROJECT_ID is set.
Build the local project context on the shared snapshot helpers and read process.env behind the pinned db test layer. Update tests that asserted the previous remote-over-env order.
Drop the reset-only seed flag overlay; the snapshot already applies SUPABASE_DB_SEED_ENABLED and the bound seed flags. Add reset tests for env enable/disable and --sql-paths over env.
…the snapshot Resolve experimental feature opt-ins through the shared key precedence with their strict 0/1 codec, and load storage, seed buckets and gen signing-key config through CliConfigValues so remote selection by env and flag/env overrides apply uniformly.
- Coerce config-tier values through the key codec into the draft before decode, so env() references, case-variant bool tokens and single-string glob lists decode as before; add a weak glob codec for db.seed.sql_paths and db.migrations.schema_paths. - Gate db.ssl_enforcement env overrides on the section, and assert every optional schema section is gated or explicitly exempt. - Add a hidden option to key.flag that keeps the config binding, and re-hide db pull --use-pg-delta. - Type context-default keys such as projectId as plain values, sanitize project_id once, and drop the project-id shims. - Require CliConfigValues in readDbToml and the db config resolver instead of building a flagless fallback; wire cliConfigValuesLayer into every command that reaches them.
…ackage dotenv loader
…ts --password flag
…s snapshot Exposes the loaded document on the snapshot, lets a load tolerate an unreadable .temp/project-ref, and carries the merged document on value failures so secrets set can salvage edge_runtime.secrets without a second loader.
Deletes the env-override and remote-wins plumbing, the project-environment loader, the deprecated CommandSettings.dbPassword and the inert resolver params. project_id now comes from the config snapshot, start reads dotenv private keys from the snapshot sources, and the modules the config foundation depends on move out of command-internal into shared/config.
Restricts process.env and Bun.env across apps/cli/src, except the provider layer, the env loader, the node shim and compute stack templates.
jgoux
left a comment
There was a problem hiding this comment.
Requesting changes for a regression: projects that only have config.json no longer get parent-directory config discovery when the workdir is defaulted. Three smaller notes are inline.
|
/ai-review |
There was a problem hiding this comment.
Superseded by a newer AI review
🤖 AI Review
Verified all seven findings from both independent reviews by reading the cited code and surrounding call paths. All seven are confirmed, with no duplicates. The most serious issue is password disclosure in validation errors; the remaining issues concern configuration consistency, validation, origin metadata, repeated warnings, and documentation.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🔴 CRITICAL | apps/cli/src/config/cli-config-key-annotations.ts:160 |
security |
codex | Malformed local database passwords are disclosed in validation errors because db.password lacks diagnostic redaction metadata. |
| 🟡 MINOR | apps/cli/src/command-internal/resolved-config-context.ts:42 |
consistency |
claude | Local resolved contexts ignore project-file hostname and Docker settings because they call getHostname without the project environment values they loaded. |
| 🟡 MINOR | apps/cli/src/config/cli-config-values.layer.ts:397 |
correctness |
codex | Environment overrides can expand twice in loaded and materialized config while get(key) expands them once, producing different values for the same key. |
| 🟡 MINOR | apps/cli/src/command-internal/db-config.toml-read.ts:701 |
validation |
codex | The database config reader no longer checks required credentials for enabled custom external providers. |
| 🟡 MINOR | apps/cli/src/config/cli-config-values.layer.ts:445 |
diagnostics |
codex | Deprecation warnings bypass runtime deduplication and repeat when another target is loaded or the caches are invalidated. |
| ⚪ NIT | apps/cli/docs/compute-commands.md:9 |
documentation |
claude | The documented boolean grammar says 'any case', but the parser rejects mixed-case spellings such as tRue. |
| ⚪ NIT | apps/cli/src/config/cli-config-values.layer.ts:422 |
correctness |
claude | materialized.originAt incorrectly attaches the applied remote to untracked values that originated in the base config. |
Stats
Claude findings: 3 · Codex findings: 4 · Confirmed: 7 · Refuted: 0 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
|
/ai-review |
There was a problem hiding this comment.
🤖 AI Review
Both independent reviews were available. Code inspection confirmed the credential-withholding defense-in-depth gap and duplicate test fixture, and refuted the secret-recovery claim because valid secret references are expanded before schema failure. Runtime tests were not run.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟡 MINOR | apps/cli/src/config/cli-config-values.layer.ts:499 |
security |
claude | projectEnvValues retains linked-target credentials even when the loader withholds those credentials from get, envValues, and decoding for a foreign project. |
| ⚪ NIT | apps/cli/src/commands/inspect/report/report.config.unit.test.ts:135 |
test-quality |
codex | The numeric rule-field rejection runs twice with an identical fixture and could be consolidated. |
Refuted findings (kept for transparency, not posted as review comments)
apps/cli/src/commands/secrets/set/set.handler.ts:201(error-handling): Omitting tolerateInvalid from the fallback load drops valid env-referenced secrets during schema recovery when an unrelated shell override is invalid.
Refuted: The fallback can fail as described, but valid secret values have already been expanded before recovery. packages/config/src/edge_runtime.ts:33-39 uses secret(), whose definition at packages/config/src/lib/env.ts:35-39 has no deferred-env annotation; the interpolation at :250-260 therefore resolves these references before decoding. set.handler.ts:80-91 preserves those expanded values during recovery, and packages/config/src/lib/resolve.ts:93-94 wraps the resulting literal secrets in Redacted even with an empty lookup. The stated trigger does not discard them.
Stats
Claude findings: 1 · Codex findings: 2 · Confirmed: 2 · Refuted: 1 · Uncertain: 0
Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
jgoux
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround. All four earlier comments are resolved: the config.json ancestor walk, functions new failing before it writes, the exact boolean spellings, and the documented inspect-rules strictness. The other fix commits look good too.
One new P2 remains: an env() reference inside an array loses its environment origin in fileDeclared, so config pull can overwrite it. There is also one small nit, both inline.
jgoux
left a comment
There was a problem hiding this comment.
Thanks, both items from the last round are fixed: 663d4a1 keeps array env() references for the decode, so config pull no longer overwrites them, and ce74130 drops the redundant withheldNames filter. Approving.
I also reviewed the overall design against what Effect 4.0.2 provides. The custom parts are justified: Effect's ConfigProvider has no per-value provenance or key listing, no env() interpolation inside documents, and no Go-compatible scalar grammar (bool spellings, base-prefixed and underscored ints, port 0). In 4.0.2, Config.withDefault also turns an invalid value with an absent fallback into the default, which would weaken validation here. The precedence resolver, dotenv/dotenvx handling, document views, and cache wiring fit that gap well. The PR's own Config usage is limited to Config.option(Config.string(...)), so moving to 4.0.2 only needs the constructor renames.
Three P3 edge cases are inline. Two optional, non-blocking design follow-ups:
readShellEnvironmentwalks the whole provider tree and needs a segment cap to stay bounded. The registry already knows every env name, so direct lookups for those, plusCliEnvNameSourcefor the dotenvx key prefix, would cover it with less code.- The leaf codecs could become Schema transformations (
Schema.decodeTo,SchemaGetter.split) shared with flags viaFlag.withSchema, giving one parser per type across flags, env, and config. The Go-compatible grammar itself stays custom.
Config-tier env() references (scalars and lists holding one) are written into every view as the key codec resolved them, with the environment origin carried over. get() and the loaded views now agree on values like ROWS=010 and ROWS=1_000, and mixed lists coerce per element again.
…hange-config-precedence-to-flags-env-configtoml-defaults # Conflicts: # apps/cli/src/command-internal/db-bootstrap/local-container-inputs.ts # apps/cli/src/command-internal/db-bootstrap/start-local-database.ts # apps/cli/src/command-internal/db-config.integration.test.ts # apps/cli/src/command-internal/db-config.layer.ts # apps/cli/src/command-internal/db-config.parse.ts # apps/cli/src/command-internal/db-config.toml-read.ts # apps/cli/src/command-internal/db-config.toml-read.unit.test.ts # apps/cli/src/command-internal/db-target-flags.ts # apps/cli/src/command-internal/db-target-flags.unit.test.ts # apps/cli/src/command-internal/docker-ids.unit.test.ts # apps/cli/src/command-internal/experimental-feature.ts # apps/cli/src/command-internal/experimental-gate.unit.test.ts # apps/cli/src/command-internal/hostname.ts # apps/cli/src/command-internal/hostname.unit.test.ts # apps/cli/src/command-internal/local-project-context.ts # apps/cli/src/command-internal/local-project-context.unit.test.ts # apps/cli/src/command-internal/pg-dump.run.ts # apps/cli/src/command-internal/pgpass.ts # apps/cli/src/command-internal/project-environment.ts # apps/cli/src/command-internal/project-environment.unit.test.ts # apps/cli/src/command-internal/seed-buckets.ts # apps/cli/src/command-internal/stack-config.ts # apps/cli/src/command-internal/supabase-env.ts # apps/cli/src/command-internal/supabase-env.unit.test.ts # apps/cli/src/commands/bootstrap/bootstrap.command.ts # apps/cli/src/commands/bootstrap/bootstrap.handler.ts # apps/cli/src/commands/config/push/push.handler.ts # apps/cli/src/commands/db/diff/diff.command.ts # apps/cli/src/commands/db/dump/SIDE_EFFECTS.md # apps/cli/src/commands/db/dump/dump.command.ts # apps/cli/src/commands/db/pull/SIDE_EFFECTS.md # apps/cli/src/commands/db/pull/pull.command.ts # apps/cli/src/commands/db/push/push.command.ts # apps/cli/src/commands/db/push/push.integration.test.ts # apps/cli/src/commands/db/remote/changes/changes.command.ts # apps/cli/src/commands/db/remote/commit/commit.command.ts # apps/cli/src/commands/db/reset/reset.command.ts # apps/cli/src/commands/db/schema/declarative/generate/generate.command.ts # apps/cli/src/commands/db/shared/pgdelta.seam.integration.test.ts # apps/cli/src/commands/experimental/compute/delete/delete.command.ts # apps/cli/src/commands/experimental/compute/list/list.command.ts # apps/cli/src/commands/experimental/compute/new/new.command.ts # apps/cli/src/commands/experimental/compute/push/push.command.ts # apps/cli/src/commands/experimental/compute/status/status.command.ts # apps/cli/src/commands/experimental/stack/start/start.handler.ts # apps/cli/src/commands/experimental/stack/start/start.native.integration.test.ts # apps/cli/src/commands/functions/deploy/deploy.command.ts # apps/cli/src/commands/functions/download/download.command.ts # apps/cli/src/commands/functions/new/new.command.ts # apps/cli/src/commands/functions/serve/serve.integration.test.ts # apps/cli/src/commands/gen/bearer-jwt/bearer-jwt.command.ts # apps/cli/src/commands/gen/gen.signing-keys-config.ts # apps/cli/src/commands/gen/signing-key/signing-key.command.ts # apps/cli/src/commands/gen/signing-key/signing-key.integration.test.ts # apps/cli/src/commands/gen/types/types.handler.ts # apps/cli/src/commands/gen/types/types.shared.ts # apps/cli/src/commands/link/link.command.ts # apps/cli/src/commands/migration/list/list.command.ts # apps/cli/src/commands/migration/repair/repair.command.ts # apps/cli/src/commands/migration/squash/squash.command.ts # apps/cli/src/commands/migration/squash/squash.native.integration.test.ts # apps/cli/src/commands/services/services-local-stack.ts # apps/cli/src/commands/services/services.command.ts # apps/cli/src/commands/start/SIDE_EFFECTS.md # apps/cli/src/commands/start/start.handler.ts # apps/cli/src/commands/storage/storage.frame.ts # apps/cli/src/config/command-settings.layer.ts # apps/cli/src/shared/functions/deploy.ts # apps/cli/src/shared/functions/download.ts # apps/cli/src/shared/functions/serve.ts # apps/cli/tests/helpers/command-mocks.ts
…hema readShellEnvironment loads only the names it is asked for plus the DOTENV_PRIVATE_KEY prefix, instead of walking the whole provider. Config codecs decode env, flag text and documents through one Schema per value type, with a single base-0 uint parser shared with parse-uint. A looping dotenv symlink or a non-directory supabase path is skipped like a missing file, .env.local is read once, and the upgrade notice reads a BOM-prefixed .env the same way the config loader does.
|
@jgoux picked up both of your optional follow-ups in 0ed30fd:
Also merged next (Effect 4.0.2) in the same push. |
Summary
Every config value now resolves in one order, in every command:
explicit flag > shell env > project env files > config (config.json over config.toml, a matched [remotes.*] block over the base) > defaultProject env files are
.env.<SUPABASE_ENV>.local,.env.local,.env.<SUPABASE_ENV>and.env. They are read fromsupabase/and then the project root, andSUPABASE_ENVdefaults todevelopment.Before this change, each command family overlaid flags, env and remotes by hand, and the families disagreed:
[remotes.*]block beat an explicitSUPABASE_*variable.config diffandconfig pushcould see different values.--passwordwas silently dropped when combined with--db-url.ADR 0001 already defined this order, so this is mostly making the code actually follow it.
Closes CLI-1885, closes CLI-2214. The decisions are recorded in ADR 0031.
How it works
Before, every command decided for itself where a setting came from, and they didn't agree. Now one service answers "what's the value of X?" for every command, and it always asks the same sources in the same order.
The problem
A setting like "should
db pushseed?" can come from five places:--include-seed)export SUPABASE_DB_SEED_ENABLED=true)supabase/.env,.env.local, ...)config.toml, where a matching[remotes.staging]block beats the base[db.seed]Each command family used to walk that list by hand, in its own order, with its own parser. So
db pushandstartcould look at the same setting and get different answers, a remote block could beat your shell env in one command but not another, andconfig diffcould show something different from whatconfig pushwould send.The service:
CliConfigValuesThink of it as a librarian. You don't go into the stacks yourself; you ask the librarian. It checks the five shelves in one fixed order and hands you the first answer it finds, with a note saying which shelf it came from.
A command uses it in three steps.
1. Load the resolved config once per run.
This reads the flags, the env and the env files, parses
config.toml, picks the matching[remotes.*]block, applies the order above and validates the whole config. If anything is invalid (for exampleSUPABASE_STUDIO_PORT=abc), the command stops here, and the error names where the bad value came from.2. Ask for a value by key.
You get the value plus its origin. That origin is what lets the CLI say things like
SUPABASE_AUTH_SITE_URL (shell) overrides auth.site_url in [remotes.staging]or(from SUPABASE_API_MAX_ROWS in supabase/.env).3. Bind flags to keys instead of reading them by hand.
The flag isn't a separate boolean the handler has to remember to merge in. It is declared as the flag tier of
db.seed.enabled, so the resolved config already knows about it.Where the keys come from
CliConfigKeysis generated from the config schema (CliConfigSchema). Every leaf inconfig.tomlbecomes a key with:db.seed.enabledSUPABASE_plus the path in upper snake case, soSUPABASE_DB_SEED_ENABLEDNobody writes the 360 or so keys by hand. Add a field to the schema and it gets an env override and a parser for free. The only hand-written parts are exceptions, such as codec overrides, exclusions and section gates like "SMTP env vars only apply if
[auth.email.smtp]exists".A worked example
--include-seedSUPABASE_DB_SEED_ENABLED.env*[remotes.staging.db.seed][db.seed](base)Seeding is on, from the flag. Without the flag, the shell value wins (still on, origin shell). Before this PR, the remote block's
falsewould have beaten the shell variable, which is the headline breaking change. And because seeding is on for a project that matched a remote block that doesn't itself turn seeding on, the CLI asks for confirmation before seeding.How it relates to
config.tomlconfig.tomlisn't replaced. It's shelf 4. The@supabase/configpackage still parses it, merges the matching remote and decodes it. The service calls those package stages and layers flags and env on top, and commands can no longer call the package loaders directly.The resolved config gives two views of the whole config:
resolvedConfig.loaded: what you declared (file plus flags and env), with no defaults.config pushandconfig diffuse this, so push never sends a default you didn't write.resolvedConfig.materialized: the same with defaults applied. Used by things that need a complete config to run, likestart, local Docker and stack.What stops this drifting back
SUPABASE_*config env name or imports the package config loaders.oxlintbansprocess.envandBun.envacross the CLI.tsc.Design
It's a big diff, sorry - but most of it is commands moving onto the new service. The interesting parts are
apps/cli/src/config/cli-config-*.ts, the guard tests incode-structure.unit.test.tsand ADR 0031. If you disagree with any of the behaviour calls in the table below, that's totally fine, I'd rather we settle them here than afternextships.CliConfigValuesis the only service that resolves values.load({ workdir, projectRef })returns a memoisedResolvedCliConfig.resolvedConfig.get(key)returns the value and the tier it came from.resolvedConfig.loadedis the declared config: file, flags and env, with no defaults.config pushandconfig diffuse it.resolvedConfig.materializedis the declared config with defaults applied.CliConfigSchema. Each leaf gets the env nameSUPABASE_+ its upper-snake path, and a codec derived from its type. Section gates, codec overrides and exclusions are hand-written annotations.key.flag(...), so "explicitly passed" is part of the type rather than a convention. If two flags set the same key to different values, the first load fails withCliConfigFlagConflictError.@supabase/config/internalexposes the pipeline stages: parse, merge a selected remote, decode. Only the foundation calls them.loadCliConfig,resolveCliConfigSubtree,loadCliProjectEnvironment) outside the foundationkey.flagoxlintbansprocess.env,Bun.env,globalThis.processandenvimported fromnode:processacrossapps/cli/src.withCliConfigFlagsfailstsc.[remotes.*]block sets, the CLI prints one WARN per key.--debuglists the origin of every non-default value.config pushlabels env-sourced rows and prints a one-line summary of them, even with--yes.CommandSettingsreadsSUPABASE_PROJECT_IDfrom the shell only.experimental-feature.tsignores remotes and env files, because it runs before the project is known.db.passwordhas no env tier.secrets settolerates an invalid unrelated value, so a broken key doesn't block rotating a secret.Behaviour changes
[remotes.*]block beatSUPABASE_*variables for the keys it setSUPABASE_PROJECT_IDand a matched remoteproject_idnamed local Docker resourcesSUPABASE_PROJECT_IDwins--include-seedandenabled = false[db.seed] enabled = falsewon--include-seed,--sql-pathsorSUPABASE_DB_SEED_ENABLED=trueoverride it.db pushstill seeds only with--include-seeddb pushanddb reset --linkedask before seeding a project that matched a[remotes.*]block, unless that block itself setsdb.seed.enabled = true.--yesorSUPABASE_YESconfirms. A non-interactive run without it exits 1 withSeedConsentRequiredErrorbefore applying anything--passwordwith--db-urlor--localdb push,db pull,db dumpanddb schema declarative generatemigration listandmigration repairnow also reject--local.migration squashtargets the local database unless you pass--linked, sosquash -pneeds--linked. An empty-p ""counts as not passedSUPABASE_DB_PASSWORDfor another projectdb diff --use-pg-delta=falsedb pull --use-pg-delta(hidden, deprecated)[db.migrations].schema_pathsaloneSUPABASE_EXPERIMENTAL_PGDELTA_ENABLED[experimental.pgdelta].enabledlike any other key, sofalseselects migra for the run.SUPABASE_EXPERIMENTAL_PG_DELTAstays ignoredSUPABASE_EXPERIMENTAL_STACKtook0/1only)1,t,T,true,True,TRUEor0,f,F,false,False,FALSE. Anything else, including mixed case liketRue, fails the commandstopandservices) withCliConfigValueError, naming the variable or file it came from.functions newalso stops before writing anything instead of falling back to defaults.secrets setis the one exceptionenabled,SUPABASE_API_PORTinfunctions serve,SUPABASE_API_SCHEMASingen types --linked/^\d+$/in one reader, octal-aware in another0x,0o,0band_separators, max 65535. A leading zero (054322) is rejectedSUPABASE_REMOTES_<NAME>_PROJECT_IDdbcommands onlysecrets set,config diff,config pushand storagestartandfunctionsfell back to the project ref; the db readers used the folder namegen types --localpasswordSUPABASE_DB_PASSWORD[db].password, like every other--localcommandservices,functions deploy,functions download,functions serveconfig.tomlonly, in placesconfig.jsonfirst, like every other commandconfig pushandconfig diffSUPABASE_*and flag overrides and show the same values. Env-sourced rows are labelled, and JSON output carriesoriginsupabase/config.tomlonly; some commands then climbed again forconfig.jsonsupabase/config.tomlorsupabase/config.json. A nestedconfig.jsonproject now wins over an outerconfig.tomloneSUPABASE_SERVICES_HOSTNAME.envbygen typesand seed buckets only, ahead of the shell.env*. The Docker daemon still comes from the shell environment only[experimental.inspect.rules]inspect reportaccepted a single table and non-string rule fields[[experimental.inspect.rules]]with string fields, as every other command already requiredlink --passwordNewly env-overridable keys
Four config keys had no
SUPABASE_*override before and now have one:auth.sms.otp_expiry(SUPABASE_AUTH_SMS_OTP_EXPIRY)auth.sms.otp_length(SUPABASE_AUTH_SMS_OTP_LENGTH)db.network_restrictions.allowed_cidrs(SUPABASE_DB_NETWORK_RESTRICTIONS_ALLOWED_CIDRS)db.network_restrictions.allowed_cidrs_v6(SUPABASE_DB_NETWORK_RESTRICTIONS_ALLOWED_CIDRS_V6)Error codes
MigrationPasswordFlagsErroris nowDbPasswordFlagsError.CliConfigValueErrorinstead ofDbConfigLoadError,SeedConfigLoadError,StatusConfigLoadErrororStopConfigLoadError.SeedConsentRequiredError.BREAKING CHANGE: Flags and
SUPABASE_*environment variables now override config, including a matched[remotes.<name>]block. Every value resolves in one order: flag > shell environment > project env files >config.json/config.toml(a matched remote block over the base) > default. Project env files are.env.<SUPABASE_ENV>.local,.env.local,.env.<SUPABASE_ENV>and.env, insupabase/and then the project root;SUPABASE_ENVdefaults todevelopment.[remotes.*]block, anySUPABASE_*variable in the job or in those files now beats the block. The CLI warns for each value it overrides. Checkenv | grep ^SUPABASE_in CI andgrep -h ^SUPABASE_ supabase/.env* .env*in the repo, and setSUPABASE_ENVin CI so.env.developmentdoesn't apply.supabase config pushnow pushes those overrides; for example, aSUPABASE_AUTH_SITE_URLin.env.localreplaces the value in[remotes.production]. Push labels each env-sourced value, andsupabase config diffshows the same values.[remotes.*]block now asks first, unless the block itself setsdb.seed.enabled = true.db push --include-seedanddb reset --linkedexit 1 in CI unless you pass--yes.--include-seedandSUPABASE_DB_SEED_ENABLED=truenow overrideenabled = falsein the base[db.seed]too.db pushstill seeds only with--include-seed.--passwordwith--localor--db-urlnow exits 1 ondb push,db pull,db dump,db schema declarative generate,migration listandmigration repair.migration squashtargets the local database by default, somigration squash -pneeds--linked. Put the password in the URL, or set[db].passwordfor the local database.SUPABASE_DB_PASSWORDis no longer sent to another project (--project-ref B). Pass--password, or provide an access token so the CLI can create a temporary login role.SUPABASE_*variable now fails every command that loads project config, includingdb push,stopandservices(for exampleSUPABASE_STUDIO_PORT=abc). The error names the variable or file and reportsCliConfigValueError.SUPABASE_EXPERIMENTAL_PGDELTA_ENABLEDoverrides[experimental.pgdelta].enabled, sofalseselects migra for the run. Boolean variables, includingSUPABASE_EXPERIMENTAL_STACK, accept1,t,T,true,True,TRUEor0,f,F,false,False,FALSEand fail on anything else.supabase db diff --use-pg-delta=falsenow turns pg-delta off even when config or env turns it on.supabase gen types --localuses[db].password, notSUPABASE_DB_PASSWORD.SUPABASE_PROJECT_IDnow names local containers even when a remote block matches. Without aproject_idin config.toml, local containers and volumes are named after the project folder, not the project ref. Runsupabase stopbefore upgrading, or setproject_id.054322) are rejected.servicesand thefunctionscommands readconfig.jsonbeforeconfig.toml.--workdir, the CLI uses the nearest directory withsupabase/config.tomlorsupabase/config.json, so a nestedconfig.jsonproject now wins over an outerconfig.tomlone.inspect reportneeds[[experimental.inspect.rules]]with string fields; a single[experimental.inspect.rules]table now fails.supabase link --passwordis deprecated and ignored.MigrationPasswordFlagsErroris nowDbPasswordFlagsError; invalid values reportCliConfigValueError; seeding without consent reportsSeedConsentRequiredError.