Skip to content

chore(repo): promote next into develop for the v3 beta - #7402

Merged
supabase-cli-releaser[bot] merged 90 commits into
developfrom
next
Oct 29, 2026
Merged

supabase-cli-releaser[bot] merged 90 commits into
developfrom
next

Conversation

@Coly010

@Coly010 Coly010 commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Promotes next into develop so develop starts publishing 3.0.0-beta.N. This is the major cut from the release process.

This one lands by approval, not the merge button. Approving fast-forwards develop to this PR's head once the required checks are green on it. If develop has moved in the meantime the fast-forward job comments and fails - the develop → next sync brings the new commits in, then it needs a re-approve.

It'd help to hold merges into develop while this is open so it doesn't move under the cut. Once it lands:

  • the push to develop publishes 3.0.0-beta.1 on the beta tag
  • breaking merges into next stay frozen for the beta soak
  • weekly deploys won't take 3.0.0 to main until the deploy PR carries release-major

🤖 Generated with Claude Code

Coly010 and others added 30 commits October 8, 2026 09:42
)

Removes the last of the Go CLI: the deprecated commands and flags the
TypeScript CLI still forwarded to a bundled `supabase-go` binary, and
the Go source tree, toolchain and packaging behind them. Targets `next`,
so it ships in the next major. The user-facing changes are in the
`BREAKING CHANGE` footer at the end, which is what the release notes
pick up.

## Notes for reviewers

- The removed commands and flags stay registered but hidden, so using
one exits 1 with its replacement rather than an unknown-command error.
Each records a `RemovedSurfaceError` telemetry fingerprint
(`removed_command` / `removed_flag`), so remaining usage can be tracked
before the stubs are deleted outright.
- `GoProxy` and its wiring in `cli/root.ts` and `shared/cli/run.ts` are
deleted.
- `apps/cli-go/` is deleted (its `LICENSE` moves to the repo root),
along with the Go CI workflows, toolchain entries and Go-specific lint,
format, knip, CODEOWNERS and dependabot config. Every platform package
ships one `supabase` binary, and the install script removes a leftover
`supabase-go`.
- The `pkg/v*` tags of `github.com/supabase/cli/pkg` are frozen and
protected by a ruleset. [ADR 0030](docs/adr/0030-go-cli-removal.md)
records the removal, supersedes ADR 0016, and lists the known external
importers of that module.

Part of CLI-2432

BREAKING CHANGE: Removed the deprecated commands and flags that still
ran through the bundled Go binary, and the `supabase-go` binary itself.
If your scripts or CI use any of these, switch to the replacement. Each
removed command or flag now exits with code 1 and prints its
replacement.

- `supabase db remote changes` → `supabase db diff --linked`
- `supabase gen keys` → `supabase projects api-keys --project-ref <ref>`
- `supabase db diff --use-pg-schema` → omit the flag to use the default
engine, or pass `--use-pg-delta`
- `supabase functions download --legacy-bundle` → `supabase functions
download --use-api`. If that also fails and the Function was deployed
with a CLI older than 1.120.0, redeploy it with the current CLI first.
- `supabase db branch create`, `delete`, `list` and `switch` → removed
with no local replacement. Hosted preview branches are managed with
`supabase branches`.
- `--create-ticket` (global flag) → removed. Report CLI problems with
`supabase issue bug`.
- `supabase-go` → no longer installed. Call `supabase` instead.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…7064)

## Summary

With the Go CLI removed on `next`, the codebase still described and
named much of its behaviour in Go terms. This PR removes that, including
user-facing error text that quoted Go library errors. Telemetry error
identity (error `_tag`s and actionability fingerprints) is unchanged;
the committed error-tag snapshot has no diff.

**Config loading**
- `@supabase/config`'s internal `goViperCompat` → `cliCompat` (still
internal-only via `@supabase/config/internal`).
- New `apps/cli/src/command-internal/cli-config-load.ts` wraps
`loadCliConfig`, `resolveCliConfigValue`, `resolveCliConfigSubtree`, and
the config-pull validation decode, always setting `cliCompat: true`. A
code-structure guard keeps other `apps/cli` files from bypassing it.
- Functions serve/deploy/download now require the CLI loader; the unused
strict fallback and its constant `config.go_compat` span attribute are
removed. Only `experimental compute` stays on the strict public loader.
Whether to drop the lenient behaviour itself is a separate discussion.

**Renames**
- Go/pflag/cobra/viper-named files and identifiers are renamed for what
they do, e.g. `go-json.ts` → `html-safe-json.ts`,
`go-output.encoders.ts` → `output.encoders.ts`, `pflag-reconcile.ts` →
`argv-flag-reconcile.ts`, `cobra-flag-groups.ts` → `flag-groups.ts`,
`viper-env.ts` → `supabase-env.ts`, `*.go-payload.ts` →
`*.response-shape.ts`, `FunctionsGoConfigCompat` →
`FunctionsLocalConfigLoader`.
- `ArgvWorkdirError`, `UrlParseError`, and `TomlEncodeError` keep their
previous tag/fingerprint/`name` strings so error tracking is unaffected.
- The `go-keyring-base64:` prefix on stored keyring tokens is unchanged
(persisted data).

**Docs, comments, test titles**
- `SIDE_EFFECTS.md` files, CLI docs, `AGENTS.md`, comments, and test
titles describe current behaviour instead of comparing against the Go
CLI. ADRs are left as historical records.
- Corrected two stale doc claims: `packages/stack` does not use the
strict config default, and `test db` honours the global `--network-id`.

## Linked issue

None; internal cleanup.

- [x] I'm a Supabase maintainer.

## Checklist

- [x] The PR title follows Conventional Commits.
- [x] Tests added or updated for the change.
- [x] `pnpm check:all` passes; relevant package tests pass for every
touched workspace.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

BREAKING CHANGE: `COBRA_COMPLETION_DESCRIPTIONS` is no longer read; set
`SUPABASE_COMPLETION_DESCRIPTIONS` to control shell completion
descriptions. Error messages that quoted Go library errors are reworded:
invalid boolean flag values (e.g. `sso add --skip-url-validation`) now
say `expected a boolean` instead of `strconv.ParseBool: …`; invalid
unsigned values for `storage cp --jobs` and `db reset --last` now say
`expected an unsigned integer` (or `value out of range`) instead of
`strconv.ParseUint: …`; oversized migration/seed statements report
`scanner: token too long` instead of `bufio.Scanner: token too long`;
`gen bearer-jwt` claim and JWK parse errors now read `invalid claims:
expected a JSON object, got <kind>`, `invalid claims: number <n> is out
of range`, `failed to parse JWK: expected a JSON object, got <kind>`,
and `invalid JWK field <field>: expected <type>, got <kind>` instead of
`json: cannot unmarshal … into Go value/struct field …`. Config type
errors name values by shape: `SUPABASE_PROFILE` files report `expected a
YAML mapping at the top level` and `array`/`object` type names, `[db]`
glob entries report TOML type names (`local date`, `offset date-time`,
`array`, `table`, …) instead of Go type names, and malformed `db
lint`/`db advisors` payloads report `invalid <field>: expected <shape>`
instead of naming Go types. `SUPABASE_PROFILE` errors are reworded
(`unknown keys: …`, `<key> is required`, `<key> must be an http(s) URL`,
`unsupported config file type …`, `no profile config file specified`)
and multi-line profile errors no longer pad lines with trailing spaces.
Config decode errors drop the `decoding failed due to the following
error(s):` header and render entries as `<key>: expected a string, got
<type>`, `<key>: unknown keys: …`, `<key>: must be one of …`. `failed to
unmarshal …` messages (postgres-config, bootstrap templates, functions
download) now read `failed to parse …`, `db lint`'s `failed to marshal
json:` reads `failed to parse lint result:`, and TOML output errors drop
the `toml:` prefix.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…7066)

## TL;DR

pg-delta becomes the schema diff engine for every project, not only ones
created by a recent `supabase init`. A `config.toml` with no
`[experimental.pgdelta]` section, or a section that omits `enabled`, now
resolves to pg-delta for `db diff`, `db pull`, and `db remote commit`.
Migra stays available through a one-line rollback.

```mermaid
flowchart LR
  subgraph Before
    A1["no [experimental.pgdelta]"] --> M1[migra]
    B1["enabled = true"] --> P1[pg-delta]
    C1["SUPABASE_EXPERIMENTAL_PG_DELTA=1"] --> P1
  end
  subgraph After
    A2["no [experimental.pgdelta]"] --> P2[pg-delta]
    B2["enabled = false"] --> M2[migra]
    C2["--use-migra / --diff-engine migra"] --> M2
  end
```

## Why

[CLI-1587](https://linear.app/supabase/issue/CLI-1587) opted new
projects into pg-delta, but existing projects still diffed with migra
unless they edited `config.toml`. This finishes the migration so every
project uses the same engine.

## What changed

- `[experimental.pgdelta].enabled` defaults to `true` when absent, in
the CLI's config reader and in `@supabase/config` (including the
published JSON schema). An explicit `enabled = false` still selects
migra.
- The historical `SUPABASE_EXPERIMENTAL_PG_DELTA` opt-in env var is no
longer read. With pg-delta on by default it added nothing, and a stale
value would have silently overridden `enabled = false`. `--use-pg-delta`
remains the per-run override.
- `db schema declarative generate` / `sync` no longer need
`--experimental`. They are closed only when `enabled = false` is set and
`--experimental` is omitted.
- A versionless `db reset --experimental` (and a fresh-volume `start
--experimental`) applies `[db.migrations].schema_paths` files only when
pg-delta is explicitly disabled. Otherwise it replays migrations and,
when `schema_paths` is set, prints a warning naming the `enabled =
false` rollback (before the remote reset's confirmation prompt).
- `db pull --declarative` writes `[db.migrations].schema_paths` into
`config.toml` only when pg-delta is explicitly disabled.
- `db diff -f` that finds declarative schema files it did not read
prints a note pointing to `supabase db schema declarative sync` (and to
`--use-migra` when migra would read those files), including when the
diff is empty. Local diffs without `-f` only report the JSON advisory.
- The `schema_paths` warning on `db diff` / `db pull` explains that
`declarative sync` reads `declarative_schema_path`, not `schema_paths`,
and names the `enabled = false` rollback. The declarative-command gate
suggests `--experimental` rather than undoing `enabled = false`.
- On the stack backend, `--use-migra=false` / `--use-pgadmin=false` are
accepted, since they keep pg-delta.
- `db diff --use-migra=false` keeps the pg-delta default instead of
selecting migra.
- Help text, command docs, and `SIDE_EFFECTS.md` contracts describe
pg-delta as the default.

## Linked issue

Closes [CLI-1588](https://linear.app/supabase/issue/CLI-1588)

## BREAKING CHANGES:

pg-delta is now the default schema diff engine for all projects.
Generated migration SQL from `db diff`, `db pull`, and `db remote
commit` changes for projects that had not opted in. Under pg-delta,
`[db.migrations].schema_paths` and declarative files in
`supabase/schemas` are no longer inputs to `db diff` or migration-style
`db pull`, so the "edit `supabase/schemas`, then `db diff -f`" workflow
no longer picks up those edits; use `supabase db schema declarative
sync` to generate migrations from declarative schema files.

To keep migra, set this in `supabase/config.toml`:

```toml
[experimental.pgdelta]
enabled = false
```

or pass `--use-migra` (`db diff`) / `--diff-engine migra` (`db pull`)
per run. `SUPABASE_EXPERIMENTAL_PG_DELTA` is ignored.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.github/workflows/cli-go-api-sync.yml
#	.github/workflows/cli-go-mirror.yml
#	CONTRIBUTING.md
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/docs/release-process.md
#	apps/cli/src/cli/complete.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-config.integration.test.ts
#	apps/cli/src/command-internal/db-config.parse.unit.test.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-connection.sql-pg.integration.test.ts
#	apps/cli/src/command-internal/db-connection.sql-pg.unit.test.ts
#	apps/cli/src/command-internal/db-image.unit.test.ts
#	apps/cli/src/command-internal/debug-logger.layer.ts
#	apps/cli/src/command-internal/debug-logger.layer.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/go-proxy.layer.ts
#	apps/cli/src/command-internal/go-proxy.layer.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/profile-load.ts
#	apps/cli/src/command-internal/profile-load.unit.test.ts
#	apps/cli/src/command-internal/project-environment.unit.test.ts
#	apps/cli/src/command-internal/stack-shadow-cache.ts
#	apps/cli/src/command-internal/string-slice-flag.ts
#	apps/cli/src/command-internal/supabase-env.ts
#	apps/cli/src/command-internal/viper-env.unit.test.ts
#	apps/cli/src/commands/db/branch/branch.command.ts
#	apps/cli/src/commands/db/branch/create/create.command.ts
#	apps/cli/src/commands/db/branch/delete/delete.command.ts
#	apps/cli/src/commands/db/branch/list/list.command.ts
#	apps/cli/src/commands/db/branch/switch/switch.command.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.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/remote/changes/changes.command.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/sync/sync.integration.test.ts
#	apps/cli/src/commands/db/test/SIDE_EFFECTS.md
#	apps/cli/src/commands/functions/download/download.command.ts
#	apps/cli/src/commands/gen/keys/keys.command.ts
#	apps/cli/src/commands/gen/types/types.command.ts
#	apps/cli/src/commands/gen/types/types.handler.ts
#	apps/cli/src/commands/start/SIDE_EFFECTS.md
#	apps/cli/src/commands/start/start.handler.ts
#	apps/cli/src/commands/storage/cp/cp.command.ts
#	apps/cli/src/shared/cli/run.unit.test.ts
#	apps/cli/src/shared/functions/deploy.ts
#	apps/cli/src/shared/functions/download.ts
#	apps/cli/src/shared/functions/serve.ts
#	apps/cli/src/shared/init/project-init.templates.unit.test.ts
#	apps/cli/src/shared/telemetry/error-actionability.ts
#	apps/cli/src/telemetry/command-telemetry.unit.test.ts
#	docs/adr/README.md
#	knip.jsonc
#	mise.lock
#	mise.toml
…ults (#7074)

## 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](docs/adr/0031-config-value-precedence.md).

## 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.**

```ts
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.**

```ts
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.**

```ts
// 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

```toml
[db.seed]
enabled = true

[remotes.staging]
project_id = "abc123"

[remotes.staging.db.seed]
enabled = false
```

```bash
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: `1`, `t`, `T`, `true`, `True`, `TRUE` or `0`, `f`, `F`,
`false`, `False`, `FALSE`. Anything else, including mixed case like
`tRue`, 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. `functions new` also stops before writing
anything instead of falling back to defaults. `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` |
| Default workdir | The walk up from the current directory looked for
`supabase/config.toml` only; some commands then climbed again for
`config.json` | One walk for every command, stopping at the nearest
`supabase/config.toml` or `supabase/config.json`. A nested `config.json`
project now wins over an outer `config.toml` one |
| `SUPABASE_SERVICES_HOSTNAME` | Read from the project `.env` by `gen
types` and seed buckets only, ahead of the shell | Read by every local
command, shell first, then project `.env*`. The Docker daemon still
comes from the shell environment only |
| `[experimental.inspect.rules]` | `inspect report` accepted a single
table and non-string rule fields | Rules must be
`[[experimental.inspect.rules]]` with string fields, as every other
command already required |
| `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 `1`,
`t`, `T`, `true`, `True`, `TRUE` or `0`, `f`, `F`, `false`, `False`,
`FALSE` 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`.
- Without `--workdir`, the CLI uses the nearest directory with
`supabase/config.toml` or `supabase/config.json`, so a nested
`config.json` project now wins over an outer `config.toml` one.
- `inspect report` needs `[[experimental.inspect.rules]]` with string
fields; a single `[experimental.inspect.rules]` table now fails.
- `supabase link --password` is deprecated and ignored.
- Error codes: `MigrationPasswordFlagsError` is now
`DbPasswordFlagsError`; invalid values report `CliConfigValueError`;
seeding without consent reports `SeedConsentRequiredError`.
#7109)

## Summary

This removes four commands that have been deprecated for at least one
stable cycle and have a direct replacement. They stay registered but are
hidden from help. Running any of them now exits 1 and names the
replacement. This uses the same `removedCommand()` tombstone as `db
remote changes` and `gen keys`.

| Command | Replacement |
| -- | -- |
| `db remote commit` | `db pull` |
| `inspect db total-index-size` | `inspect db index-stats` |
| `inspect db total-table-sizes` | `inspect db table-stats` |
| `inspect db table-index-sizes` | `inspect db table-stats` |

- The tombstones still accept their old flags, so existing invocations
reach the removal error instead of a parse error. The inspect tombstones
open no DB connection.
- Each tombstone records a `RemovedSurfaceError` telemetry fingerprint
(`removed_command`), so remaining usage stays visible.
- Their handlers, handler tests and per-command docs pages are deleted.
SIDE_EFFECTS.md and the `DOCS_EXCLUDED` table are updated.
- The pg-delta config description no longer mentions `db remote commit`,
and `config.schema.json` is regenerated.
- The higher-use `inspect db` aliases (`table-sizes`, `index-sizes`,
`cache-hit`, …) are unchanged.

The matching "removed in v3" warning text for stable is in #7108
(develop).

## Linked issue

Part of CLI-2595

## Checklist

- [x] The PR title follows Conventional Commits.
- [x] Tests added or updated: `hidden-flag.unit.test.ts` runs all four
tombstones and asserts the removal message and suggestion, and checks
they're absent from `inspect db --help`.
- [x] From the repository root, `pnpm check:all` passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

BREAKING CHANGE: `supabase db remote commit` is removed; use `supabase
db pull`. `supabase inspect db total-index-size` is removed; use
`supabase inspect db index-stats`. `supabase inspect db
total-table-sizes` and `supabase inspect db table-index-sizes` are
removed; use `supabase inspect db table-stats`. Each removed command
exits 1 and prints its replacement.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.oxlintrc.effect.json
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/src/command-internal/db-bootstrap/local-db-running.ts
#	apps/cli/src/command-internal/db-pull-run.ts
#	apps/cli/src/command-internal/docker-ids.ts
#	apps/cli/src/command-internal/docker-ids.unit.test.ts
#	apps/cli/src/command-internal/local-project-context.ts
#	apps/cli/src/command-internal/pgdelta.ts
#	apps/cli/src/command-internal/seed-buckets.ts
#	apps/cli/src/command-internal/storage-content-type.unit.test.ts
#	apps/cli/src/command-internal/storage-credentials.ts
#	apps/cli/src/command-internal/storage-url.ts
#	apps/cli/src/commands/backups/list/list.integration.test.ts
#	apps/cli/src/commands/backups/restore/restore.integration.test.ts
#	apps/cli/src/commands/branches/create/create.integration.test.ts
#	apps/cli/src/commands/branches/delete/delete.integration.test.ts
#	apps/cli/src/commands/branches/get/get.integration.test.ts
#	apps/cli/src/commands/branches/list/list.integration.test.ts
#	apps/cli/src/commands/branches/pause/pause.integration.test.ts
#	apps/cli/src/commands/branches/update/update.handler.ts
#	apps/cli/src/commands/completion/bash/bash.integration.test.ts
#	apps/cli/src/commands/completion/completion-scripts.unit.test.ts
#	apps/cli/src/commands/completion/fish/fish.integration.test.ts
#	apps/cli/src/commands/completion/powershell/powershell.integration.test.ts
#	apps/cli/src/commands/completion/zsh/zsh.integration.test.ts
#	apps/cli/src/commands/config/config.load.ts
#	apps/cli/src/commands/config/diff/diff.integration.test.ts
#	apps/cli/src/commands/config/pull/pull.integration.test.ts
#	apps/cli/src/commands/config/push/push.handler.ts
#	apps/cli/src/commands/config/push/push.integration.test.ts
#	apps/cli/src/commands/db/diff/diff.handler.ts
#	apps/cli/src/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/commands/db/remote/commit/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/remote/commit/commit.command.ts
#	apps/cli/src/commands/db/remote/commit/commit.handler.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.handler.ts
#	apps/cli/src/commands/db/schema/declarative/sync/sync.handler.ts
#	apps/cli/src/commands/db/shared/pgdelta.seam.layer.ts
#	apps/cli/src/commands/domains/activate/activate.integration.test.ts
#	apps/cli/src/commands/domains/create/create.integration.test.ts
#	apps/cli/src/commands/domains/delete/delete.integration.test.ts
#	apps/cli/src/commands/domains/get/get.integration.test.ts
#	apps/cli/src/commands/domains/reverify/reverify.integration.test.ts
#	apps/cli/src/commands/experimental/compute/compute.shared.ts
#	apps/cli/src/commands/experimental/compute/list/list.integration.test.ts
#	apps/cli/src/commands/feedback/add/add.integration.test.ts
#	apps/cli/src/commands/feedback/delete/delete.integration.test.ts
#	apps/cli/src/commands/functions/delete/delete.integration.test.ts
#	apps/cli/src/commands/functions/deploy/deploy.integration.test.ts
#	apps/cli/src/commands/functions/download/download.handler.ts
#	apps/cli/src/commands/functions/download/download.integration.test.ts
#	apps/cli/src/commands/functions/list/list.encoders.ts
#	apps/cli/src/commands/functions/list/list.integration.test.ts
#	apps/cli/src/commands/functions/new/new.integration.test.ts
#	apps/cli/src/commands/functions/serve/serve.integration.test.ts
#	apps/cli/src/commands/gen/bearer-jwt/bearer-jwt.claims.ts
#	apps/cli/src/commands/gen/bearer-jwt/bearer-jwt.integration.test.ts
#	apps/cli/src/commands/gen/gen.signing-keys-config.ts
#	apps/cli/src/commands/gen/signing-key/signing-key.integration.test.ts
#	apps/cli/src/commands/gen/types/types.integration.test.ts
#	apps/cli/src/commands/inspect/db/inspect-deprecated.integration.test.ts
#	apps/cli/src/commands/inspect/db/table-index-sizes/table-index-sizes.command.ts
#	apps/cli/src/commands/inspect/db/table-index-sizes/table-index-sizes.handler.ts
#	apps/cli/src/commands/inspect/db/total-index-size/total-index-size.command.ts
#	apps/cli/src/commands/inspect/db/total-index-size/total-index-size.handler.ts
#	apps/cli/src/commands/inspect/db/total-table-sizes/total-table-sizes.command.ts
#	apps/cli/src/commands/inspect/db/total-table-sizes/total-table-sizes.handler.ts
#	apps/cli/src/commands/logout/logout.integration.test.ts
#	apps/cli/src/commands/network-bans/get/get.integration.test.ts
#	apps/cli/src/commands/network-bans/network-bans.experimental-gate.integration.test.ts
#	apps/cli/src/commands/network-bans/remove/remove.integration.test.ts
#	apps/cli/src/commands/network-restrictions/get/get.integration.test.ts
#	apps/cli/src/commands/network-restrictions/network-restrictions.cidr.unit.test.ts
#	apps/cli/src/commands/network-restrictions/network-restrictions.experimental-gate.integration.test.ts
#	apps/cli/src/commands/network-restrictions/update/update.integration.test.ts
#	apps/cli/src/commands/notebooks/notebooks.integration.test.ts
#	apps/cli/src/commands/orgs/create/create.integration.test.ts
#	apps/cli/src/commands/orgs/list/list.integration.test.ts
#	apps/cli/src/commands/postgres-config/delete/delete.command.unit.test.ts
#	apps/cli/src/commands/postgres-config/postgres-config.experimental-gate.integration.test.ts
#	apps/cli/src/commands/postgres-config/postgres-config.integration.test.ts
#	apps/cli/src/commands/postgres-config/update/update.command.unit.test.ts
#	apps/cli/src/commands/projects/api-keys/api-keys.integration.test.ts
#	apps/cli/src/commands/projects/create/create.integration.test.ts
#	apps/cli/src/commands/projects/delete/delete.integration.test.ts
#	apps/cli/src/commands/projects/list/list.integration.test.ts
#	apps/cli/src/commands/pull/pull.integration.test.ts
#	apps/cli/src/commands/secrets/list/list.integration.test.ts
#	apps/cli/src/commands/secrets/set/set.integration.test.ts
#	apps/cli/src/commands/secrets/unset/unset.integration.test.ts
#	apps/cli/src/commands/snippets/download/download.handler.ts
#	apps/cli/src/commands/snippets/download/download.integration.test.ts
#	apps/cli/src/commands/snippets/list/list.integration.test.ts
#	apps/cli/src/commands/ssl-enforcement/get/get.integration.test.ts
#	apps/cli/src/commands/ssl-enforcement/update/update.integration.test.ts
#	apps/cli/src/commands/sso/add/add.command.unit.test.ts
#	apps/cli/src/commands/sso/add/add.handler.ts
#	apps/cli/src/commands/sso/add/add.integration.test.ts
#	apps/cli/src/commands/sso/info/info.integration.test.ts
#	apps/cli/src/commands/sso/list/list.integration.test.ts
#	apps/cli/src/commands/sso/remove/remove.integration.test.ts
#	apps/cli/src/commands/sso/show/show.integration.test.ts
#	apps/cli/src/commands/sso/sso.string-slice-flags.integration.test.ts
#	apps/cli/src/commands/sso/update/update.command.unit.test.ts
#	apps/cli/src/commands/sso/update/update.handler.ts
#	apps/cli/src/commands/sso/update/update.integration.test.ts
#	apps/cli/src/commands/storage/cp/cp.command.integration.test.ts
#	apps/cli/src/commands/storage/ls/ls.integration.test.ts
#	apps/cli/src/commands/storage/rm/rm.integration.test.ts
#	apps/cli/src/commands/storage/storage.frame.ts
#	apps/cli/src/commands/vanity-subdomains/vanity-subdomains.integration.test.ts
#	apps/cli/src/commands/whoami/whoami.integration.test.ts
# Conflicts:
#	apps/cli/src/command-internal/db-pull-run.ts
#	apps/cli/src/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/commands/domains/SIDE_EFFECTS.md
#	apps/cli/src/commands/domains/activate/activate.integration.test.ts
#	apps/cli/src/commands/domains/create/create.integration.test.ts
#	apps/cli/src/commands/domains/delete/delete.integration.test.ts
#	apps/cli/src/commands/domains/get/get.integration.test.ts
#	apps/cli/src/commands/domains/reverify/reverify.integration.test.ts
#	apps/cli/src/telemetry/linked-project-cache.integration.test.ts
# Conflicts:
#	apps/cli/src/command-internal/db-pull-run.ts
#	apps/cli/src/command-internal/migrate-and-seed.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/migration-history.unit.test.ts
#	apps/cli/src/command-internal/pgdelta.ts
#	apps/cli/src/command-internal/pgdelta.unit.test.ts
#	apps/cli/src/command-internal/seed-ops.unit.test.ts
#	apps/cli/src/command-internal/seed.unit.test.ts
#	apps/cli/src/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/commands/start/start.lifecycle.e2e.test.ts
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/src/commands/bootstrap/bootstrap.handler.ts
#	apps/cli/src/commands/bootstrap/bootstrap.templates.integration.test.ts
#	apps/cli/src/commands/db/advisors/advisors.format.ts
#	apps/cli/src/commands/db/advisors/advisors.integration.test.ts
#	apps/cli/src/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/commands/db/dump/dump.handler.ts
#	apps/cli/src/commands/db/dump/dump.integration.test.ts
#	apps/cli/src/commands/db/lint/lint.format.ts
#	apps/cli/src/commands/db/lint/lint.integration.test.ts
#	apps/cli/src/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/commands/db/push/push.integration.test.ts
#	apps/cli/src/commands/db/query/query.format.ts
#	apps/cli/src/commands/db/query/query.integration.test.ts
#	apps/cli/src/commands/db/reset/reset.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/sync/sync.integration.test.ts
#	apps/cli/src/commands/db/shared/pgdelta.seam.integration.test.ts
#	apps/cli/src/commands/db/start/start.integration.test.ts
#	apps/cli/src/commands/db/test/test.integration.test.ts
#	apps/cli/src/commands/experimental/stack/stack-forwarding.integration.test.ts
#	apps/cli/src/commands/init/init.integration.test.ts
#	apps/cli/src/commands/inspect/report/report.config.ts
#	apps/cli/src/commands/link/link.integration.test.ts
#	apps/cli/src/commands/migration/down/down.integration.test.ts
#	apps/cli/src/commands/migration/fetch/fetch.integration.test.ts
#	apps/cli/src/commands/migration/list/list.integration.test.ts
#	apps/cli/src/commands/migration/migration.integration.test.ts
#	apps/cli/src/commands/migration/new/new.integration.test.ts
#	apps/cli/src/commands/migration/repair/repair.integration.test.ts
#	apps/cli/src/commands/migration/squash/squash.diff.unit.test.ts
#	apps/cli/src/commands/migration/squash/squash.integration.test.ts
#	apps/cli/src/commands/seed/buckets/buckets.flags.unit.test.ts
#	apps/cli/src/commands/seed/buckets/buckets.integration.test.ts
#	apps/cli/src/commands/services/services.integration.test.ts
#	apps/cli/src/commands/start/start.gates.ts
#	apps/cli/src/commands/start/start.handler.ts
#	apps/cli/src/commands/start/start.integration.test.ts
#	apps/cli/src/commands/start/start.string-slice-flags.integration.test.ts
#	apps/cli/src/commands/status/status.command.unit.test.ts
#	apps/cli/src/commands/status/status.integration.test.ts
#	apps/cli/src/commands/status/status.string-slice-flags.integration.test.ts
#	apps/cli/src/commands/stop/stop.integration.test.ts
#	apps/cli/src/commands/test/new/new.integration.test.ts
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/src/commands/db/advisors/advisors.integration.test.ts
#	apps/cli/src/commands/db/diff/diff.command.ts
#	apps/cli/src/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/commands/db/dump/dump.command.ts
#	apps/cli/src/commands/db/pull/pull.command.ts
#	apps/cli/src/commands/db/pull/pull.integration.test.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/commit/commit.command.ts
#	apps/cli/src/commands/db/reset/reset.command.ts
#	apps/cli/src/commands/db/reset/reset.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.command.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/sync/sync.command.ts
#	apps/cli/src/commands/db/start/start.command.ts
#	apps/cli/src/commands/db/start/start.integration.test.ts
#	apps/cli/src/commands/db/test/test.integration.test.ts
#	apps/cli/src/commands/seed/buckets/buckets.integration.test.ts
# Conflicts:
#	.github/actions/setup/action.yml
#	.github/workflows/test.yml
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/docs/supabase/db/diff.md
#	apps/cli/docs/supabase/db/pull.md
#	apps/cli/src/command-internal/db-bootstrap/bootstrap-config.ts
#	apps/cli/src/command-internal/db-config.toml-read.ts
#	apps/cli/src/command-internal/db-pull-run.ts
#	apps/cli/src/command-internal/local-config-values.ts
#	apps/cli/src/command-internal/local-project-context.ts
#	apps/cli/src/command-internal/stack-config.ts
#	apps/cli/src/commands/bootstrap/SIDE_EFFECTS.md
#	apps/cli/src/commands/bootstrap/bootstrap.handler.ts
#	apps/cli/src/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/diff/diff.handler.ts
#	apps/cli/src/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/commands/db/dump/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/dump/dump.handler.ts
#	apps/cli/src/commands/db/push/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/push/push.handler.ts
#	apps/cli/src/commands/db/reset/reset.handler.ts
#	apps/cli/src/commands/db/schema/declarative/generate/generate.handler.ts
#	apps/cli/src/commands/experimental/stack/start/start.handler.ts
#	apps/cli/src/commands/gen/types/SIDE_EFFECTS.md
#	apps/cli/src/commands/gen/types/types.handler.ts
#	apps/cli/src/commands/migration/down/down.handler.ts
#	apps/cli/src/commands/services/SIDE_EFFECTS.md
#	apps/cli/src/commands/services/services-local-stack.ts
#	apps/cli/src/commands/services/services.handler.ts
#	apps/cli/src/commands/status/status.handler.ts
#	apps/cli/src/commands/status/status.integration.test.ts
#	packages/config/src/io.ts
next removed apps/cli-go, so the Go build, cache, and keyring steps synced from develop fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next already removed the deprecated config, env vars and import maps that #7154
warns about, so its warnings, env alias and tests do not apply here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the pre-renumber Go CLI removal and config precedence ADRs and point
config precedence references at ADR 0032.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	apps/cli/src/command-internal/test-db.integration.test.ts
#	apps/cli/src/commands/inspect/db/table-index-sizes/table-index-sizes.command.ts
#	apps/cli/src/commands/inspect/db/total-index-size/total-index-size.command.ts
#	apps/cli/src/commands/inspect/db/total-table-sizes/total-table-sizes.command.ts
#	apps/cli/src/commands/inspect/report/report.integration.test.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
github-actions Bot and others added 20 commits October 20, 2026 01:51
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
…utation consent rule (#7188)

## Summary

Commands that can run against either the local stack or a hosted project
didn't agree on what they target by default, and each took a different
mix of `--local`, `--linked`, `--project-ref` and `--db-url`. Hosted
mutations also each had their own idea of when to ask. This makes both
consistent: every command that can run locally runs locally unless you
pass `--remote`, and every hosted mutation follows one consent rule.

Design: [CLI-2695: one `--remote`
flag](https://linear.app/supabase/document/cli-2695-one-remote-flag-6110a06b977e).
Decisions and enforcement: ADR 0033.

**Targets**

- One flag, `--remote`, on every command that can reach a hosted
project:

  | Value | Means |
  |---|---|
| none (`--remote`) | The linked project (`SUPABASE_PROJECT_ID`, then
the link file) |
| `true`, `yes`, `on`, `1`, `y` | The linked project, as bare `--remote`
|
| `false`, `no`, `off`, `0`, `n` | Rejected: leave `--remote` out
instead |
  | 20 lowercase letters | That project ref |
| `postgres://…` / `postgresql://…` | That database (database commands
only) |
| anything else | A branch name or branch UUID of the linked project,
resolved with `supabase link`'s rule (a branch UUID also works without a
link) |

- `--remote=<value>` always works. `--remote <value>` is rewritten to
the `=` form before parsing wherever it's unambiguous. When a bare
`--remote` is followed by a word the command takes as an operand and
that word is, or nearly is, a branch name, or is shaped like a project
ref, the command stops before doing anything and shows both readings.
`--remote` given twice with different values fails before anything runs.
- Database commands (db, migration, inspect, storage, seed buckets, test
db, gen types, db schema declarative generate) default to the local
stack. In a linked checkout, a bare run says `default; pass --remote for
<project>`.
- Hosted-only commands (functions, secrets, config, branches, sso, …)
keep the linked project as their default and take
`--remote=<ref|branch>`. `branches *` takes `--remote=<ref>` for the
parent project.
- `--linked`, `--project-ref`, `--db-url` and `gen types --project-id`
still work, routed through `--remote`, and print a deprecation warning
on stderr naming the `--remote` replacement and v4 as the removal
release. `--local` is accepted, does nothing, and warns. They're hidden
from help.
- `db diff --from/--to` take the same values plus `local` and
`migrations`; `remote` is the linked project and `linked` is deprecated.
The deprecated flags still work alongside them and warn: `--project-ref`
points the `remote` side at that project, and the others change nothing.
- A hosted database whose direct host is unreachable falls back to its
connection pooler: first the pooler URL `supabase link` saved, then the
project's pooler config from the Management API. The linked project used
to try only the saved URL.
- `--remote=<default branch>` is reported as production, not as a
branch. A branch UUID used without a link is reported by its project ref
alone, since the CLI can't tell whether it's the default branch.
- Every command declares its target group with `withTargetPolicy`
(local-only, account, hosted, selectable); a tree test fails if a leaf
doesn't. Help shows a `TARGET` section and labels subcommands local only
/ remote only / local or remote.
- Commands that resolve a target print one `Target: …` line on stderr
(`db diff --from/--to` prints one per database, in order), and JSON
results carry a `target` object.

**Hosted consent**

- One rule for every hosted mutation: ask in an interactive terminal,
otherwise require `--yes` or a shell-exported `SUPABASE_YES`. A
`SUPABASE_YES` from `supabase/.env` or a working-directory `.env`
doesn't count, stdin is never read as an answer, and agents never get a
prompt.
- Replacing live state defaults to No. Only create-only operations
(projects, branches, orgs and domains create, and bootstrap) default to
Yes.
- Commands that used to ask several questions (db push, db reset with
seeding, config push, functions deploy --prune) ask one, after showing
everything they'll change.
- Network bans are only removed automatically under `--yes`, and a
failed removal is reported as that failure.
- `pull` asks once, up front, on every run except `--dry-run`. `db pull`
asks when it would update the remote migration history. Declining either
aborts before any file is written.

**New**

- `db query --read-only` accepts only read statements (`SELECT`, `WITH`,
`VALUES`, `TABLE`, `SHOW`, `EXPLAIN` or a parenthesised query), runs
them in a read-only transaction with a short lock timeout (or the
Management API's `read_only` mode on a hosted target), and skips
consent. Functions with side effects called from a `SELECT` still run,
and the docs say so.

The companion PR to `develop`,
#7186, warns on bare runs of every
command whose default flips here.

## Linked issue

Closes CLI-2695

🤖 Generated with [Claude Code](https://claude.com/claude-code)

BREAKING CHANGE: Commands that can run locally now default to the local
stack, `--remote` replaces the target flags, and every hosted mutation
follows one consent rule.

- `db push`, `db pull`, `migration fetch`, `db dump`, `migration list`,
`migration repair`, `inspect db *`, `inspect report`, `gen types` and
`storage ls/cp/mv/rm` now target the local stack when run without a
target flag. Add `--remote` to reach the linked project (`supabase db
push --remote`), `--remote=<ref>` for another project,
`--remote=<branch>` for a branch of the linked project, or
`--remote=<postgres-url>` for a database URL. `SUPABASE_PROJECT_ID` on
its own no longer selects a hosted project for these commands; it only
says which project bare `--remote` means.
- `gen types` with no target generates from the local database instead
of opening a project picker or failing. In a linked checkout with no
local stack running, it suggests `--remote`. `db schema declarative
generate` no longer asks which target to use. `db schema declarative
sync` with no declarative files generates them from the local database
and no longer offers the linked project as the source; run `supabase db
schema declarative generate --remote` first to start from the linked
project.
- `--linked`, `--project-ref`, `--db-url` and `gen types --project-id`
are deprecated: they still work and print a warning on stderr naming the
`--remote` form. `--local` does nothing and warns. v4 removes all of
them, along with `db diff`'s `linked` (use `remote`). They no longer
appear in help.
- `--remote=<value>` can't be combined with any of the deprecated flags.
`--project-ref` on a database command no longer needs `--linked`. An
empty `--project-ref=` is now an error instead of being ignored.
`--password` only applies to a hosted target.
- `db diff` rejects `--remote` together with `--from`/`--to`; use
`--from=remote` or `--to=remote`.
- `db query` rejects SQL passed both as an argument and with `--file`.
- Bare `--remote` (and the deprecated `--linked`) opens the project
picker in an interactive terminal when the checkout isn't linked.
Database and storage commands used to fail.
- `--remote <word>` after a command that takes an operand fails with a
"did you mean" error when `<word>` is, or is close to, a branch name of
the linked project, when it's shaped like a project ref, or when the
branch list can't be fetched. Put the operand before `--remote`
(`supabase db pull add_profiles --remote`) or use `--remote=<branch>`.
- Every hosted mutation now asks for confirmation, and fails with
`HostedMutationConsentRequiredError` in non-interactive, JSON,
stream-json, agent or `-o json|yaml|toml|env` runs unless `--yes` (or
`SUPABASE_YES` exported in the shell) is set. Commands that newly need
this for unattended runs:
- db push; db pull when it updates the remote migration history; pull,
except with `--dry-run`
  - migration up/down/squash/repair against a hosted target
  - db reset and test db against a hosted target
  - db query against a hosted target (or pass `--read-only` for reads)
  - seed buckets and storage cp/mv/rm against a hosted target
  - functions deploy/delete; secrets set/unset; config push
  - branches create/delete/disable/pause/unpause/update; backups restore
- encryption update-root-key; network-bans remove; network-restrictions
update; ssl-enforcement update
  - postgres-config update/delete; sso add/update/remove
  - vanity-subdomains activate/delete; domains create/activate/delete
- notebooks push; projects create/delete; orgs create; bootstrap;
feedback delete
  - compute push/delete
- A `y` piped on stdin no longer confirms a hosted mutation.
- The default answer is No for every hosted mutation except projects,
branches, orgs and domains create, and bootstrap. Pressing Enter now
declines db push, deploys, secrets changes and migration history
updates.
- `SUPABASE_YES` in `supabase/.env`, or in a `.env`, `.env.local` or
`.env.<NODE_ENV>[.local]` file in the working directory, no longer
confirms hosted mutations. When a working-directory file defines it at
all, a shell-exported `SUPABASE_YES` doesn't count either, since the CLI
can't tell the two apart. Use `--yes`.
- db push, db reset against a hosted target (including seeding), config
push and functions deploy --prune each ask one combined question instead
of several. Deploy and prune are confirmed before anything is deployed,
and `--prune` now runs even when every function is already up to date.
`secrets unset --all` lists the secrets before asking.
- Declining a hosted mutation fails with `HostedMutationDeclinedError`
("context canceled"). The command-specific cancelled error codes are
removed. Declining `db pull` or `pull` aborts before any file is
written; there's no longer a files-only path.
- Removing network bans automatically, while connecting to a hosted
database, now needs `--yes`. Otherwise the command fails and points to
`supabase network-bans remove`.
- `migration down` and `migration repair --all` on the local database no
longer prompt.
- Commands that resolve a target print `Target: …` once on stderr,
including read-only commands against a hosted project. `config push` no
longer prints its own "Pushing config to" line. JSON object results and
error envelopes gain a `target` object; `db reset`'s `target` changes
from a string to that object, and `config push` JSON no longer reports
`declined_addons` or a skipped status. Arrays and generated artifacts
are unchanged.
- `migration up` against a hosted target reports "Remote database is up
to date." instead of "Local database is up to date.", and `migration
list` labels the history column "Local DB" when the target is local.
- Agents never get a confirmation prompt, even with a terminal attached;
they need `--yes` for hosted mutations.
Keeps next's target selection (ProjectRefResolver, target/*, --remote) and
develop's on-disk link state (link snapshot over .temp or .link.json), now
under shared/config. Ports develop's stack-mode version locking, PGSSL/TLS
handling, config parse errors and token-permission help onto next's APIs,
and drops develop's v3 local-default warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merged resolver reads the pooler URL from the target, not the TOML reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/docs/supabase/db/pull.md
#	apps/cli/src/commands/branches/create/create.live.test.ts
#	apps/cli/tests/helpers/branches-live.ts
# Conflicts:
#	apps/cli-go/pkg/config/templates/Dockerfile
# Conflicts:
#	.github/workflows/cli-go-ci.yml
#	.github/workflows/cli-go-tag-pkg.yml
#	apps/cli/docs/release-process.md
#	mise.toml
#7297)

## TL;DR

parallel `db advisors --remote` runs no longer break each other with
`28P01`

## whats improved/fixed?

before calling the advisors API it set up a database login it never used

every setup resets the shared `cli_login_postgres` password, so two runs
at once (or one next to another remote db command) knocked each other
out, and on a network without IPv6 one failed after about 150 s

## how?

the remote path now skips the database and goes straight to the advisors
API

this brings it in line with `db query --remote` since #7188, so both
remote read commands work the same way

```mermaid
flowchart LR
    run["db advisors --remote"]
    run -->|before, no DB password| mint["new temp login role<br/>resets the shared password"]
    mint -- IPv6 --> api1["advisors API"]
    mint -- no IPv6 --> wait["pooler retries the login"]
    wait -- another run reset it --> fail["fails with 28P01"]
    wait -- no clash --> api1
    run -- now --> ref["resolve project ref<br/>or branch"]
    ref --> token["check access token"]
    token -- missing --> login["asks to run supabase login"]
    token -- ok --> api2["advisors API<br/>security and/or performance"]
    api2 --> out["filter by --level<br/>print, exit by --fail-on"]
```

## ref:

- related: #7281
- missed in: #7188

BREAKING CHANGE: `db advisors --remote` no longer connects to the
database first (matches `db query --remote`), so it no longer resolves
database settings, creates a temporary login role or removes network
bans under `--yes`
…-2782, CLI-2783) (#7312)

## Summary

Adds a `[db.migrations.checks]` section to `config.toml`. Each of the
nine migration security
rules gets a severity (`error`, `warn` or `off`), and
`[db.migrations.checks.exclude]` lists
`"schema.name"` objects to skip per rule. Nothing reads the policy yet;
the migration apply
path consumes it in a follow-up.

- Unknown rule names, invalid severities and malformed exclusion entries
fail config loading
with path-scoped messages, including a "did you mean" suggestion for
misspelled rules. This
section is stricter than the rest of the config so an older CLI never
ignores a rule a
  teammate set.
- An absent section leaves every rule unset; the CLI resolver treats
unset rules as `warn`.
- The section is published in the CLI config JSON Schema. `CliConfig`
gains
`db.migrations.checks`; the new constants are exported from
`@supabase/config/internal` only.

## Linked issue

Closes CLI-2782
Closes CLI-2783
Part of CLI-2786
Part of CLI-2787
next removed the Go CLI, so cli-go-ci.yml and cli-go-tag-pkg.yml go too,
and sync-repair.yml no longer listens for the deleted CI workflow.
# Conflicts:
#	apps/cli-go/api/v1-openapi.yaml
#	apps/cli-go/pkg/api/types.gen.go
#	apps/cli-go/pkg/config/templates/Dockerfile
#	apps/cli/src/command-internal/db-bootstrap/local-db-running.ts
#	apps/cli/src/command-internal/db-bootstrap/start-local-database.ts
#	apps/cli/src/command-internal/db-config.layer.ts
#	apps/cli/src/command-internal/db-config.toml-read.ts
#	apps/cli/src/commands/config/push/push.handler.ts
#	apps/cli/src/commands/db/advisors/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/advisors/advisors.integration.test.ts
#	apps/cli/src/commands/db/query/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/query/query.handler.ts
#	apps/cli/src/commands/db/query/query.integration.test.ts
#	apps/cli/src/commands/db/schema/declarative/generate/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/schema/declarative/sync/SIDE_EFFECTS.md
#	apps/cli/src/commands/db/shared/pgdelta.seam.integration.test.ts
#	apps/cli/src/commands/db/start/SIDE_EFFECTS.md
#	apps/cli/src/commands/link/SIDE_EFFECTS.md
#	apps/cli/src/commands/start/SIDE_EFFECTS.md
#	apps/cli/tests/helpers/command-mocks.ts
#	packages/config/AGENTS.md
#	packages/config/README.md
Hosted mutations now refuse in a non-TTY run without --yes, so the live
suite's remaining unconfirmed setup and cleanup calls failed with
"Confirmation required". Pass --yes to db query (write statements),
migration repair and postgres-config delete in the live tests and helpers.
@Coly010
Coly010 requested review from a team and supabase-oss as code owners October 29, 2026 20:52
@Coly010 Coly010 self-assigned this Oct 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

CLI benchmark: no changes found

Commit 465b1d8 against develop at bcd3ec9 · 10 runs each · report only, never blocks merging

Command Before After Change
supabase --version 224 ms 238 ms no real change (+6%, within normal variation)
supabase --help 235 ms 231 ms no real change
supabase init 205 ms 216 ms no real change
supabase migration new bench 212 ms 210 ms no real change
supabase gen types --lang typescript --project-id replayfixtureprojref 237 ms 248 ms no real change
supabase projects list 223 ms 218 ms no real change
supabase functions list --project-ref replayfixtureprojref 226 ms 233 ms no real change

API calls: unchanged for all 7 commands.

Binary size: 134.0 MB, up 172.0 KB from 133.8 MB.

Details: timing ranges, API calls per command, slowest phases

Timing ranges

Command Before range After range Change Verdict
supabase --version 202-253 ms 216-256 ms +13 ms (+6%) within noise
supabase --help 209-286 ms 208-269 ms -4 ms (-2%) within noise
supabase init 187-250 ms 202-227 ms +10 ms (+5%) within noise
supabase migration new bench 191-245 ms 196-262 ms -2 ms (-1%) within noise
supabase gen types --lang typescript --project-id replayfixtureprojref 214-315 ms 229-260 ms +11 ms (+5%) within noise
supabase projects list 185-236 ms 187-235 ms -5 ms (-2%) within noise
supabase functions list --project-ref replayfixtureprojref 222-245 ms 216-266 ms +7 ms (+3%) within noise

API calls per command

Command Before After Endpoint changes
supabase --version 0 0
supabase --help 0 0
supabase init 0 0
supabase migration new bench 0 0
supabase gen types --lang typescript --project-id replayfixtureprojref 2 2
supabase projects list 1 1
supabase functions list --project-ref replayfixtureprojref 2 2

Slowest phases

No command moved beyond noise, so there are no phase deltas.

Comment /bench to run it again on the latest commit.

Workflow run

@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

Verified all six findings against the checked-out code. Five are confirmed: a reproduced hosted-consent bypass, two repeated --remote validation gaps, double config interpolation, and an obsolete environment assignment. The stale Turbo exclusion is verified, but its alleged runtime failure remains uncertain. No findings overlap.

Findings

Severity Location Category Sources Claim
🔴 CRITICAL apps/cli/src/command-internal/hosted-consent/hosted-consent.yes.ts:49 security codex A dotenv parser mismatch allows file-sourced SUPABASE_YES to bypass hosted-write consent.
🟠 MAJOR apps/cli/src/shared/cli/remote-argv.ts:149 target-safety codex Repeated bare --remote flags validate only the first candidate operand, allowing later ambiguous target words to escape validation.
🟠 MAJOR apps/cli/src/shared/cli/remote-argv.ts:128 argument-validation codex Invalid later --remote values can be ignored instead of preventing hosted mutations.
🟡 MINOR apps/cli/src/shared/functions/serve.ts:717 correctness codex functions serve interpolates already-resolved auth environment references a second time.
🟡 MINOR .github/scripts/flaky-check/plan.ts:222 ci claude The flaky-check unit suite retains an exclusion for the deleted @supabase/cli-go package, potentially causing Turbo to reject the invocation.
⚪ NIT .github/scripts/flaky-check/cli.ts:116 dead-code claude The flaky-check runner still assigns SUPABASE_GO_BINARY to a deleted binary path, although no remaining code consumes the variable.

Findings outside the diff

  • 🟡 MINOR .github/scripts/flaky-check/plan.ts:222 — The flaky-check unit suite retains an exclusion for the deleted @supabase/cli-go package, potentially causing Turbo to reject the invocation.
  • ⚪ NIT .github/scripts/flaky-check/cli.ts:116 — The flaky-check runner still assigns SUPABASE_GO_BINARY to a deleted binary path, although no remaining code consumes the variable.

Stats

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


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/shared/cli/remote-argv.ts Resolved
Comment thread apps/cli/src/shared/cli/remote-argv.ts Resolved
Comment thread apps/cli/src/shared/functions/serve.ts Resolved
@supabase-cli-releaser
supabase-cli-releaser Bot merged commit 465b1d8 into develop Oct 29, 2026
169 of 172 checks passed
@supabase-cli-releaser
supabase-cli-releaser Bot deleted the next branch October 29, 2026 21:08
@Coly010
Coly010 restored the next branch October 29, 2026 21:41

This branch was successfully deployed

No deployments
config-release — 465b1d86 Deployed Oct 29, 2026 by supabase-cli-releaser[bot] via Publish #47
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.

4 participants