Skip to content

feat(cli)!: resolve config values as flags > env > config.toml > defaults - #7074

Open
Coly010 wants to merge 65 commits into
nextfrom
columferry/cli-1885-change-config-precedence-to-flags-env-configtoml-defaults
Open

Coly010 wants to merge 65 commits into
nextfrom
columferry/cli-1885-change-config-precedence-to-flags-env-configtoml-defaults

Conversation

@Coly010

@Coly010 Coly010 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

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) > default

Project env files are .env.<SUPABASE_ENV>.local, .env.local, .env.<SUPABASE_ENV> and .env. They are read from supabase/ and then the project root, and SUPABASE_ENV defaults to development.

Before this change, each command family overlaid flags, env and remotes by hand, and the families disagreed:

  • A matched [remotes.*] block beat an explicit SUPABASE_* variable.
  • Some commands ignored variables that others honoured.
  • config diff and config push could see different values.
  • --password was 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 push seed?" can come from five places:

  1. a flag you typed (--include-seed)
  2. your shell (export SUPABASE_DB_SEED_ENABLED=true)
  3. a project env file (supabase/.env, .env.local, ...)
  4. config.toml, where a matching [remotes.staging] block beats the base [db.seed]
  5. a built-in default

Each command family used to walk that list by hand, in its own order, with its own parser. So db push and start could look at the same setting and get different answers, a remote block could beat your shell env in one command but not another, and config diff could show something different from what config push would send.

The service: CliConfigValues

Think 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.

flag  >  shell env  >  project .env*  >  config.toml (remote over base)  >  default

A command uses it in three steps.

1. Load the resolved config once per run.

const configValues = yield* CliConfigValues
const resolvedConfig = yield* configValues.load({ workdir, projectRef })

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 example SUPABASE_STUDIO_PORT=abc), the command stops here, and the error names where the bad value came from.

2. Ask for a value by key.

const enabled = yield* resolvedConfig.get(CliConfigKeys.db.seed.enabled)
// enabled.value  → true
// enabled.origin → { tier: "shell", envName: "SUPABASE_DB_SEED_ENABLED" }

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.

// push.command.ts
includeSeed: CliConfigKeys.db.seed.enabled.flag({
  name: "include-seed",
  description: "Include seed data from your config.",
}),

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

CliConfigKeys is generated from the config schema (CliConfigSchema). Every leaf in config.toml becomes a key with:

  • its path: db.seed.enabled
  • its env name: SUPABASE_ plus the path in upper snake case, so SUPABASE_DB_SEED_ENABLED
  • a parser chosen from its type: boolean, port, list, and so on

Nobody 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

[db.seed]
enabled = true

[remotes.staging]
project_id = "abc123"

[remotes.staging.db.seed]
enabled = false
SUPABASE_DB_SEED_ENABLED=true supabase db push --project-ref abc123 --include-seed
Shelf Says Used?
flag --include-seed true ✅ wins
shell SUPABASE_DB_SEED_ENABLED true
project .env* unset
[remotes.staging.db.seed] false
[db.seed] (base) true
default false

Seeding is on, from the flag. Without the flag, the shell value wins (still on, origin shell). Before this PR, the remote block's false would 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.toml

config.toml isn't replaced. It's shelf 4. The @supabase/config package 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 push and config diff use 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, like start, local Docker and stack.

What stops this drifting back

  • A guard test fails if any file outside the service mentions a SUPABASE_* config env name or imports the package config loaders.
  • oxlint bans process.env and Bun.env across the CLI.
  • A command that reads config without wiring the service in fails tsc.
  • A contract test checks every key in the registry against the order.

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 in code-structure.unit.test.ts and 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 after next ships.

  • One service. CliConfigValues is the only service that resolves values.
    • load({ workdir, projectRef }) returns a memoised ResolvedCliConfig.
    • resolvedConfig.get(key) returns the value and the tier it came from.
    • resolvedConfig.loaded is the declared config: file, flags and env, with no defaults. config push and config diff use it.
    • resolvedConfig.materialized is the declared config with defaults applied.
    • The whole config is decoded on load, so an invalid value fails the command before it does anything.
  • Generated key registry. The registry is generated from CliConfigSchema. Each leaf gets the env name SUPABASE_ + its upper-snake path, and a codec derived from its type. Section gates, codec overrides and exclusions are hand-written annotations.
  • Flags. A flag binds to a key with 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 with CliConfigFlagConflictError.
  • Package stages. @supabase/config/internal exposes the pipeline stages: parse, merge a selected remote, decode. Only the foundation calls them.
  • Guardrails:
    • A code-structure test fails on:
      • registry env names written as string literals outside the foundation files
      • imports of the package loaders (loadCliConfig, resolveCliConfigSubtree, loadCliProjectEnvironment) outside the foundation
      • the old overlay helpers
      • registry-backed flags declared without key.flag
    • oxlint bans process.env, Bun.env, globalThis.process and env imported from node:process across apps/cli/src.
    • A command that reads config values without withCliConfigFlags fails tsc.
  • Visibility:
    • When a shell or project env value beats a value that the matched [remotes.*] block sets, the CLI prints one WARN per key.
    • --debug lists the origin of every non-default value.
    • config push labels env-sourced rows and prints a one-line summary of them, even with --yes.
  • Exceptions kept on purpose:
    • CommandSettings reads SUPABASE_PROJECT_ID from the shell only.
    • experimental-feature.ts ignores remotes and env files, because it runs before the project is known.
    • db.password has no env tier.
    • secrets set tolerates an invalid unrelated value, so a broken key doesn't block rotating a secret.
    • The service-role key isn't scoped to the linked project; only the database password is.

Behaviour changes

Change Before Now
Env vs matched remote A [remotes.*] block beat SUPABASE_* variables for the keys it set Flags and env beat the block, with a WARN for each key they override
SUPABASE_PROJECT_ID and a matched remote The remote's project_id named local Docker resources SUPABASE_PROJECT_ID wins
--include-seed and enabled = false A remote's or the base's [db.seed] enabled = false won --include-seed, --sql-paths or SUPABASE_DB_SEED_ENABLED=true override it. db push still seeds only with --include-seed
Seed confirmation None db push and db reset --linked ask before seeding a project that matched a [remotes.*] block, unless that block itself sets db.seed.enabled = true. --yes or SUPABASE_YES confirms. A non-interactive run without it exits 1 with SeedConsentRequiredError before applying anything
--password with --db-url or --local Silently ignored by db push, db pull, db dump and db schema declarative generate Exits 1 with a message saying where the password goes instead. migration list and migration repair now also reject --local. migration squash targets the local database unless you pass --linked, so squash -p needs --linked. An empty -p "" counts as not passed
SUPABASE_DB_PASSWORD for another project Sent to whichever project was targeted Withheld when the target differs from the linked project. A notice names both refs and says a temporary login role is used instead. Unlinked directories and same-project targets keep using the variable
db diff --use-pg-delta=false No effect against env or config Turns pg-delta off over env and config. The stack backend always uses pg-delta
db pull --use-pg-delta (hidden, deprecated) Selected the declarative export only Also turns pg-delta on for the run, so the export leaves [db.migrations].schema_paths alone
SUPABASE_EXPERIMENTAL_PGDELTA_ENABLED Not read Overrides [experimental.pgdelta].enabled like any other key, so false selects migra for the run. SUPABASE_EXPERIMENTAL_PG_DELTA stays ignored
Boolean variables Each reader had its own parser (SUPABASE_EXPERIMENTAL_STACK took 0/1 only) One grammar everywhere: true/false, 1/0, t/f in any case. Anything else fails the command
Invalid config value Failed only the commands that read it, with a command-specific error Fails every command that loads the config (including stop and services) with CliConfigValueError, naming the variable or file it came from. secrets set is the one exception
Env applies wherever a key is read Some readers ignored variables others honoured, e.g. storage and realtime enabled, SUPABASE_API_PORT in functions serve, SUPABASE_API_SCHEMAS in gen types --linked One behaviour per key, and validation runs on the effective values. Env for a key inside an optional section (webhooks, SMS providers, captcha, SMTP, ...) still applies only when that section exists
Ports /^\d+$/ in one reader, octal-aware in another One parser: decimal, 0x, 0o, 0b and _ separators, max 65535. A leading zero (054322) is rejected
SUPABASE_REMOTES_<NAME>_PROJECT_ID Worked for db commands only Selects the remote in every command, including secrets set, config diff, config push and storage
Local Docker project id start and functions fell back to the project ref; the db readers used the folder name Always the sanitised folder name, never the ref
gen types --local password Read SUPABASE_DB_PASSWORD Uses [db].password, like every other --local command
services, functions deploy, functions download, functions serve Read config.toml only, in places Read config.json first, like every other command
config push and config diff Push sent the declared document; diff read the file on its own Both apply SUPABASE_* and flag overrides and show the same values. Env-sourced rows are labelled, and JSON output carries origin
link --password Listed in help Hidden, deprecated and ignored with a warning. It never connected to the database

Newly 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

  • MigrationPasswordFlagsError is now DbPasswordFlagsError.
  • Invalid values report CliConfigValueError instead of DbConfigLoadError, SeedConfigLoadError, StatusConfigLoadError or StopConfigLoadError.
  • Seeding without consent reports the new 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, in supabase/ and then the project root; SUPABASE_ENV defaults to development.

  • If you deploy to a project with a [remotes.*] block, any SUPABASE_* variable in the job or in those files now beats the block. The CLI warns for each value it overrides. Check env | grep ^SUPABASE_ in CI and grep -h ^SUPABASE_ supabase/.env* .env* in the repo, and set SUPABASE_ENV in CI so .env.development doesn't apply.
  • supabase config push now pushes those overrides; for example, a SUPABASE_AUTH_SITE_URL in .env.local replaces the value in [remotes.production]. Push labels each env-sourced value, and supabase config diff shows the same values.
  • Seeding a project that matches a [remotes.*] block now asks first, unless the block itself sets db.seed.enabled = true. db push --include-seed and db reset --linked exit 1 in CI unless you pass --yes. --include-seed and SUPABASE_DB_SEED_ENABLED=true now override enabled = false in the base [db.seed] too. db push still seeds only with --include-seed.
  • --password with --local or --db-url now exits 1 on db push, db pull, db dump, db schema declarative generate, migration list and migration repair. migration squash targets the local database by default, so migration squash -p needs --linked. Put the password in the URL, or set [db].password for the local database.
  • In a directory linked to project A, SUPABASE_DB_PASSWORD is 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.
  • An invalid value in config or in a SUPABASE_* variable now fails every command that loads project config, including db push, stop and services (for example SUPABASE_STUDIO_PORT=abc). The error names the variable or file and reports CliConfigValueError.
  • SUPABASE_EXPERIMENTAL_PGDELTA_ENABLED overrides [experimental.pgdelta].enabled, so false selects migra for the run. Boolean variables, including SUPABASE_EXPERIMENTAL_STACK, accept true/false, 1/0 or t/f and fail on anything else.
  • supabase db diff --use-pg-delta=false now turns pg-delta off even when config or env turns it on.
  • supabase gen types --local uses [db].password, not SUPABASE_DB_PASSWORD.
  • SUPABASE_PROJECT_ID now names local containers even when a remote block matches. Without a project_id in config.toml, local containers and volumes are named after the project folder, not the project ref. Run supabase stop before upgrading, or set project_id.
  • Ports with a leading zero (054322) are rejected.
  • services and the functions commands read config.json before config.toml.
  • supabase link --password is deprecated and ignored.
  • Error codes: MigrationPasswordFlagsError is now DbPasswordFlagsError; invalid values report CliConfigValueError; seeding without consent reports SeedConsentRequiredError.

Coly010 added 30 commits October 8, 2026 14:20
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.
…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.
…napshot

functions serve, functions new, gen types, inspect report, compute and the db toml
reader resolve flag > shell > project .env > config > default through CliConfigValues.
Removes the private serve .env loader, resolveLocalProjectId, valueErrorMessage and
snapshot declaredAt; folds the sanitizeProjectId copies into shared/config/project-id.
…snapshot

Partial [auth.email.smtp] tables load again, a config-tier env() reference
keeps its origin on numeric and boolean keys, load-time warnings print once
per runtime, config push counts only values it will send, and config pull
writes go through writeThrough. The remaining package loaders move behind the
CliConfigValues snapshot.
Extend the oxlint restrictions to env imports, namespace imports and
globalThis.process, list the audited ambientEnvironment callers, and make the
code-structure guard reject registry env names as literals and package loader
imports outside the foundation.
…hange-config-precedence-to-flags-env-configtoml-defaults

# Conflicts:
#	apps/cli/src/command-internal/argv-flag-reconcile.ts
#	apps/cli/src/command-internal/config-validate.ts
#	apps/cli/src/command-internal/config-validate.unit.test.ts
#	apps/cli/src/command-internal/db-bootstrap/bootstrap-config.ts
#	apps/cli/src/command-internal/db-bootstrap/local-container-inputs.ts
#	apps/cli/src/command-internal/db-bootstrap/shadow-cache.ts
#	apps/cli/src/command-internal/db-bootstrap/start-local-database.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/docker-ids.unit.test.ts
#	apps/cli/src/command-internal/experimental-gate.unit.test.ts
#	apps/cli/src/command-internal/global-flags.ts
#	apps/cli/src/command-internal/local-config-values.ts
#	apps/cli/src/command-internal/local-config-values.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/migration-apply.ts
#	apps/cli/src/command-internal/migration-apply.unit.test.ts
#	apps/cli/src/command-internal/pg-dump.run.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/stack-shadow-cache.ts
#	apps/cli/src/command-internal/supabase-env.ts
#	apps/cli/src/command-internal/viper-env.unit.test.ts
#	apps/cli/src/commands/config/config.load.ts
#	apps/cli/src/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/dump/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/dump/dump.handler.ts
#	apps/cli/src/commands/db/dump/dump.integration.test.ts
#	apps/cli/src/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/pull/pull.command.ts
#	apps/cli/src/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/commands/db/reset/SIDE_EFFECTS.md
#	apps/cli/src/commands/functions/deploy/SIDE_EFFECTS.md
#	apps/cli/src/commands/functions/deploy/deploy.integration.test.ts
#	apps/cli/src/commands/functions/download/SIDE_EFFECTS.md
#	apps/cli/src/commands/functions/download/download.integration.test.ts
#	apps/cli/src/commands/functions/new/new.handler.ts
#	apps/cli/src/commands/functions/serve/SIDE_EFFECTS.md
#	apps/cli/src/commands/functions/serve/serve.handler.ts
#	apps/cli/src/commands/functions/serve/serve.integration.test.ts
#	apps/cli/src/commands/gen/bearer-jwt/bearer-jwt.handler.ts
#	apps/cli/src/commands/gen/gen.signing-keys-config.ts
#	apps/cli/src/commands/gen/signing-key/signing-key.handler.ts
#	apps/cli/src/commands/gen/types/SIDE_EFFECTS.md
#	apps/cli/src/commands/gen/types/types.handler.ts
#	apps/cli/src/commands/inspect/report/report.config.ts
#	apps/cli/src/commands/inspect/report/report.config.unit.test.ts
#	apps/cli/src/commands/link/SIDE_EFFECTS.md
#	apps/cli/src/commands/link/link.integration.test.ts
#	apps/cli/src/commands/migration/list/SIDE_EFFECTS.md
#	apps/cli/src/commands/migration/repair/SIDE_EFFECTS.md
#	apps/cli/src/commands/migration/squash/SIDE_EFFECTS.md
#	apps/cli/src/commands/migration/squash/squash.handler.ts
#	apps/cli/src/commands/secrets/set/set.handler.ts
#	apps/cli/src/commands/seed/buckets/buckets.integration.test.ts
#	apps/cli/src/commands/services/SIDE_EFFECTS.md
#	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/shared/cli/code-structure.unit.test.ts
#	apps/cli/src/shared/functions/deploy.ts
#	apps/cli/src/shared/functions/download.ts
#	apps/cli/src/shared/functions/functions-config.ts
#	apps/cli/src/shared/functions/serve.ts
#	apps/cli/src/telemetry/telemetry-state.layer.unit.test.ts
#	packages/config/docs/cli-config-loading.md
#	packages/config/src/io.ts
@Coly010
Coly010 requested a review from a team as a code owner October 9, 2026 09:26

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by a newer AI review

🤖 AI Review

Verified all seven findings from both independent reviews. Confirmed six, including two major regressions: stale config snapshots across functions serve restarts and missing decryption of stack Edge Runtime secrets. Refuted the numeric-boolean complaint using pre-existing coercion code and tests. Verification used code inspection and isolated codec execution; full runtime tests were not run.

Findings

Severity Location Category Sources Claim
🟠 MAJOR apps/cli/src/command-internal/stack-config.ts:411 correctness codex Stack startup forwards encrypted Edge Runtime secrets as ciphertext, breaking functions that require their decrypted values.
🟠 MAJOR apps/cli/src/config/cli-config-values.layer.ts:473 caching codex functions serve reuses its initial config and project dotenv values across watched restarts, so external edits no longer take effect.
🟡 MINOR apps/cli/src/config/cli-config-values.layer.ts:331 validation codex The snapshot accepts non-object JSON config roots as an empty/default config instead of rejecting them.
🟡 MINOR apps/cli/src/config/cli-config-key.ts:121 correctness claude uintCodec accepts string integers above Number.MAX_SAFE_INTEGER and silently rounds some of them, while rejecting the corresponding typed numeric config values.
🟡 MINOR apps/cli/src/commands/gen/gen.signing-keys-config.ts:345 diagnostics codex Signing-key guidance identifies config.toml even when the command loaded config.json.
⚪ NIT apps/cli/src/command-internal/experimental-feature.ts:87 documentation codex The experimental-feature resolver documentation incorrectly describes its shell override as accepting strictly 0 and 1.
Refuted findings (kept for transparency, not posted as review comments)
  • apps/cli/src/config/cli-config-key.ts:138 (correctness): boolCodec.fromConfig treats non-zero numbers, including fractions and NaN, as true instead of rejecting them, unlike the stricter environment-string grammar.
    Refuted: The coercion occurs, but treating it as invalid behavior conflicts with the established weak decoding contract. The default-branch resolveBool explicitly accepts numeric truthiness, and local-config-values.unit.test.ts:1776–1779 explicitly expects both 123 and 1.5 to become true. The PR retains the same rule in local-config-values.ts:678–681; typed numeric values and environment strings intentionally use different grammars.

Stats

Claude findings: 2 · Codex findings: 5 · Confirmed: 6 · Refuted: 1 · Uncertain: 0


Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: auto · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread apps/cli/src/command-internal/stack-config.ts Outdated
Comment thread apps/cli/src/config/cli-config-values.layer.ts Outdated
Comment thread apps/cli/src/config/cli-config-values.layer.ts
Comment thread apps/cli/src/config/cli-config-key.ts
Comment thread apps/cli/src/commands/gen/gen.signing-keys-config.ts Outdated
Comment thread apps/cli/src/command-internal/experimental-feature.ts Outdated
@Coly010

Coly010 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

All five supplied findings are confirmed, with corrections to the reset load-order detail and duplicate-prompt scope. No findings overlap. Verification covered the reported issues and related code paths, not the entire 470-file PR.

Findings

Severity Location Category Sources Claim
🟠 MAJOR apps/cli/src/config/cli-config-values.layer.ts:191 configuration codex A shell-only dotenvx private key with a high numeric suffix, such as DOTENV_PRIVATE_KEY_2026, can be omitted from the environment snapshot, causing otherwise valid encrypted configuration to fail decryption.
🟡 MINOR apps/cli/src/command-internal/db-config.toml-read.ts:334 error-handling claude The database TOML reader wraps CliConfigValueError and CliConfigFlagConflictError as DbConfigLoadError, losing their error identity. This affects db push and the linked database resolver used by db reset, despite the separate password-resolution path preserving those tags.
🟡 MINOR apps/cli/src/command-internal/db-target-flags.ts:263 correctness claude An empty password flag is excluded from configuration assignments but remains Some("") in parsed handler flags, so the direct-target guard incorrectly rejects it with --local, --db-url, or a default-local target.
🟡 MINOR apps/cli/src/command-internal/seed-remote-consent.ts:42 correctness codex A matched remote declaring db.seed.enabled = 1 enables seeding through the configuration codec but is incorrectly treated as lacking explicit seed authorization, potentially rejecting a run that cannot prompt.
⚪ NIT apps/cli/src/command-internal/db-push-core.ts:233 ux claude Interactive db push --include-seed asks about seeding twice when pending seeds target a matched remote requiring consent: once before writes and again after migrations.

Stats

Claude findings: 3 · Codex findings: 2 · Confirmed: 5 · 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.

Comment thread apps/cli/src/command-internal/db-config.toml-read.ts Outdated
Comment thread apps/cli/src/command-internal/db-target-flags.ts Outdated
Comment thread apps/cli/src/command-internal/db-push-core.ts
Comment thread apps/cli/src/config/cli-config-values.layer.ts
Comment thread apps/cli/src/command-internal/seed-remote-consent.ts Outdated
…hange-config-precedence-to-flags-env-configtoml-defaults

# Conflicts:
#	apps/cli/src/command-internal/db-config.toml-read.ts
#	apps/cli/src/command-internal/db-pull-run.ts
#	apps/cli/src/command-internal/diff-engine.ts
#	apps/cli/src/command-internal/diff-engine.unit.test.ts
#	apps/cli/src/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/diff/diff.handler.ts
#	apps/cli/src/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/commands/db/reset/reset.handler.ts
#	apps/cli/src/commands/db/reset/reset.integration.test.ts
Drop the SUPABASE_EXPERIMENTAL_PG_DELTA alias and the env-alias machinery only it used.
Read experimental.pgdelta.enabled through the resolved config in the db config reader and
the diff handler. Keep the pgdelta section exempt from env section gating so the env
rollback works without the section. Update goldens, tests and docs for the pg-delta default.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant