Skip to content

chore: production deploy - #6874

Merged
supabase-cli-releaser[bot] merged 71 commits into
mainfrom
develop
Sep 30, 2026
Merged

supabase-cli-releaser[bot] merged 71 commits into
mainfrom
develop

Conversation

@supabase-cli-releaser

Copy link
Copy Markdown
Contributor

spydon and others added 19 commits September 25, 2026 13:16
…ves undeclared (#6827)

## Summary

Running `bun scripts/build-binary.ts` directly in `apps/cli` fails with
`Could not resolve: "@libpg-query/parser"` from
`plpgsql-deparser/esm/hydrate.js`, on `develop` as well as on feature
branches. `pnpm build:binary` and the turbo `supabase#build` task
succeed, which is why CI never sees it.

The cause is in the dependency, not the build script.
`plpgsql-deparser@0.7.13` imports `@libpg-query/parser` in
`esm/hydrate.js` but declares only `@pgsql/types` and `pgsql-deparser`;
the package that does declare the parser is its parent,
`plpgsql-parser`. With pnpm's strict layout the deparser cannot see an
undeclared package. It resolves under `pnpm run` only because pnpm
exports `NODE_PATH` pointing at `node_modules/.pnpm/node_modules`, the
hidden hoist directory, and Bun consults `NODE_PATH` when resolving
imports. Invoke Bun without that environment and the fallback is gone.

This adds a `packageExtensions` entry for `plpgsql-deparser` declaring
`@libpg-query/parser` with the same range `plpgsql-parser` uses, so it
dedupes to the already installed and patched `17.6.10`. That is the
mechanism the workspace file already uses for `bun-types` and
`@supabase/pg-delta`, which import undeclared packages in the same way.

## What changed

- `pnpm-workspace.yaml`: the `packageExtensions` entry.
- `pnpm-lock.yaml`: the `packageExtensionsChecksum` and the deparser's
dependency list.

Verified by deleting `apps/cli/dist/supabase` and running `bun
scripts/build-binary.ts` with `NODE_PATH` and `NODE_OPTIONS` unset; the
binary builds and runs. A frozen-lockfile install passes.

## Linked issue

No GitHub issue; found while building #6822 locally. The
upstream fix would be `plpgsql-deparser` declaring the dependency
itself; this entry can go once a release does.
…estroy (#6825)

## Problem

With Docker unavailable, `supabase stack start --runtime docker` failed
and printed
`Failed to stop stack host <id>: ...`, leaving a stale `runtime: docker`
registration behind. A
follow-up `stack start --runtime native` for the same project then
failed with a runtime-mismatch
error, and the suggested `stack stop`/`stack destroy` failed too, since
both need an owner that
can't start without Docker.

This PR fixes both failure paths so a stack never gets stuck this way.

Refs:
https://linear.app/supabase/issue/CLI-2500/clean-up-stacks-when-docker-is-unavailable-on-start

## Case 1: `stack start` leaves a stack behind when Docker is
unavailable

`Stack.create` saved the new stack's registration before ever starting
its owner. When the first
owner-backed call then failed to launch (Docker unreachable), the
registration was already saved,
and cleanup (`stack.stop`) failed too, because it also needs the owner
that couldn't start.

`create` now takes a `startOwner` option: when set, it launches the
owner as part of creation and
rolls back the registration it just saved if that launch fails or is
interrupted. The rollback
keeps the stack when an owner holds it: one that reported ready before
an interrupt, or one another
caller launched in the meantime.
`stack start` opts into this for new stacks, so a Docker failure at
creation now surfaces as one
clean error, with no separate "Failed to stop" diagnostic and nothing
left registered.

**Test it:**
```sh
DOCKER_HOST=tcp://127.0.0.1:1 SUPABASE_EXPERIMENTAL_STACK=1 supabase stack start --runtime docker
# one Docker error, no "Failed to stop stack host" line, no leftover ~/.supabase/stacks/<id>/

SUPABASE_EXPERIMENTAL_STACK=1 supabase stack start --runtime native
# starts cleanly, no runtime-mismatch error
```

## Case 2: `stack destroy` fails outright when Docker is unavailable

`stack destroy` also needs to start an owner to do the cleanup, and that
owner's startup re-runs
the same container sweep `start` does. With Docker down, that sweep
fails before an owner exists,
so `destroy` failed with a raw `Cause([Fail(StackHostError: ...)])`
message and left the stack
registered.

`destroy` now falls back to removing the stack's registration and host
data itself when no owner
is running and the container engine can't be reached:

- **What counts as unreachable:** the engine reports its daemon isn't
listening (`Cannot connect to
the Docker daemon`, `connection refused`, a missing socket such as
Docker 29's
`connect: no such file or directory`, or the Windows daemon-down forms
of `error during connect`),
or the `docker`/`podman` CLI isn't installed. Generic wrappers that also
carry authentication
failures, such as `Cannot connect to Podman`, only count when their
underlying cause is one of
those. Permission, TLS, authentication, and timeout errors still fail
and keep the stack
registered, as does a reachable engine that fails to remove a container.
- **Concurrent owners:** under the state lock, destroy checks whether
anything holds the stack's
control port and refuses to delete if an owner started in the meantime.
- **Host data only the engine can remove:** if any directory under the
stack's data can't be
deleted by the current user (for example, database files a container
wrote as its own user on
Docker below 26), destroy fails before removing anything and asks to
start the engine and retry.
- **What's left behind:** the stack's containers, and any database data
kept in the shared Docker
volume. `destroy` returns `{ runtimeCleanup: "skipped", engine,
cleanupCommands }`, with one
command that removes the containers (filtered by stack id and stack
root, and succeeding when none
  are left) and one per database volume directory.

CLI behaviour in the fallback:

- `stack destroy` exits 0, warns on stderr with the cleanup commands,
and prints
`Stack <id> was removed locally; its Docker resources remain until the
commands above are run.`
  instead of `Stack <id> destroyed.`
- JSON output adds `runtimeCleanup` (`complete` or `skipped`), plus
`engine` and `cleanupCommands`
  when skipped, next to the existing `destroyed` and `id`.
- Shadow-database commands (`db diff`, `db pull`, `migration squash`,
declarative sync) print the
  same cleanup commands when their temporary stack's destroy is skipped.

**Test it:**
```sh
SUPABASE_EXPERIMENTAL_STACK=1 supabase stack start --runtime docker
SUPABASE_EXPERIMENTAL_STACK=1 supabase stack stop
DOCKER_HOST=tcp://127.0.0.1:1 SUPABASE_EXPERIMENTAL_STACK=1 supabase stack destroy --yes
# exits 0, lists the cleanup commands on stderr, and removes the local registration
SUPABASE_EXPERIMENTAL_STACK=1 supabase stack start --runtime native
# starts cleanly, no runtime-mismatch error
# with Docker running again, the printed commands remove the leftover containers and database data
```

## Coverage

- `packages/stack/src/create.owner-rollback.integration.test.ts` (new):
rollback on a failed or
interrupted owner launch during `create`, and a same-identity retry with
a different runtime.
- `packages/stack/src/destroy.runtime-unavailable.integration.test.ts`
(new): offline removal and
exact cleanup commands for an unreachable daemon, Docker 29's
missing-socket message, a Windows
daemon-down message, and a missing CLI; the printed container command
run against an unreachable
and an empty engine; a rejected listing, a Podman authentication
failure, undeletable host data,
and a reachable-engine cleanup failure all still fail and keep the
registration.
- `packages/stack/src/HostProcess.integration.test.ts`: the control-port
check used by both
  rollbacks.
- `apps/cli/.../start/start.integration.test.ts`: a new stack whose
owner can't reach Docker,
  through the real stack API, leaves nothing registered.
- `apps/cli/.../destroy/destroy.runtime-unavailable.integration.test.ts`
(new): text and JSON output
  for a skipped cleanup.
- `apps/cli/src/command-internal/stack-shadow.integration.test.ts`:
shadow commands print the
  cleanup commands.
- `apps/cli/.../start/start.e2e.test.ts`: one compiled-binary golden
path for the Docker-unavailable
  create failure.

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

---------

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

## TL;DR

fixes `dblink` connections as `postgres` on the native stack and makes
its database port check passwords like Docker.

## whats broken?

the native postgres only listens on its unix socket, and the bundled
`pg_hba.conf` trusts every local connection. so `dblink` as `postgres`
was refused for not using a password,
and the database port accepted any password.

## now fixed by:

the native launch writes its own hba file into the socket directory and
points `hba_file` at it.

local connections now authenticate with `scram-sha-256`, except
`supabase_admin`
which the stack uses to bootstrap roles. a wrong password on the native
database port now fails, as it does on Docker..

## ref:

- closes: #6832
## TL;DR

fixes auth refusing to start on the stack when `timebox` or
`inactivity_timeout` is `0s`

## whats broken?

the stack forwarded zero session limits to auth as is.
auth rejects a zero duration, so it exited on boot and every auth
request returned 502.

## now fixed by:

zero session limits are left out of the auth environment, so `0s` means
no limit. every other value passes through unchanged.

## ref:

- closes: #6845
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This PR was automatically created to sync the generated `@supabase/api`
package with the latest Management API OpenAPI document.

Changes were detected in the upstream OpenAPI documents exposed by
`https://api.supabase.com/api/v1-json` and
`https://api.supabase.com/api/v2-json`.

Co-authored-by: jgoux <1443499+jgoux@users.noreply.github.com>
## TL;DR

Local Edge Functions no longer turn early responses to uploads into `502
Bad Gateway`.

## whats introduced?

When the functions main service answered before reading the request
body, the runtime closed the connection with the body unread. The main
service answers early either itself, for example a JWT `401` or a `404`,
or when the function returns without reading the body. The gateway's
next body write then hit a reset, and it replied `502`, discarding the
response it had already received.

- both functions bootstraps (`apps/cli` and `packages/stack`) now own
the request body: the worker gets its own stream, and whatever it leaves
unread is drained before the response is returned
- early responses to requests with a body are now sent once the body has
been received, which behind Kong was already the case
- integration tests cover a worker that abandons the body and the
bootstrap's own `401` and `404` rejections, checking that the body is
read to the end and never cancelled
- the offline e2e sends larger bodies for the early function rejection
and the JWT rejection through Kong

## ref:

- related: #6564

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Problem

The PostgreSQL image sets no stop signal, so `docker stop` sends
SIGTERM. PostgreSQL treats SIGTERM as a "smart" shutdown and waits for
every client to disconnect. With stack services still connected, `docker
stop` waits out Docker's grace period, the container is killed, and the
next start runs crash recovery.

## Change

- Create the database container with `--stop-signal SIGINT`,
PostgreSQL's fast shutdown. The official postgres image uses the same
default.
- A container integration test asserts the signal and a clean exit.

The native runtime already sent SIGINT.

## Stack

Part 1 of 8 of the stack package simplification, based on develop.
Review and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown ←
this PR
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Fresh database setup runs Auth, Storage, and Realtime initialization
commands concurrently, waits for successful completion, and then applies
the database overlay and project SQL. It no longer creates temporary
service instances or launches temporary servers for catalog setup.

The stack exposes finite work through `stack.commands.run`, including
PostgreSQL clients and typed initialization commands. The host owns
streamed output, cancellation, failure diagnostics, and cleanup.
Initialization reuses the service recipes and saved stack credentials;
services retain their long-running lifecycle.

Pin Realtime `v2.134.5` to the republished image containing the one-shot
preparation script from
supabase/slim-services#324.

Pin Functions to Edge Runtime `v1.77.1`, which fixes the SQLite
cache-initialization race that caused cold-start SIGBUS crashes
([upstream fix](supabase/edge-runtime#747)).
Remove the Functions test transport retry so dropped connections fail
directly.
## Problem

Several failure modes in the local runtime left stacks stuck or
unreachable:

- Health was checked once per launch and the failure was cached. One
slow first boot left a running service that returned 502 until it was
restarted.
- Native services reserved a port by binding and releasing it, then
launched on it. Only Pooler recovered from the resulting collision.
- Every saved stack's port claims blocked other stacks, running or not.
Switching git branch, or opening a second worktree, failed on the
classic fixed ports until the other stack was destroyed.
- One unreadable `state.json` in the state root blocked port allocation
and owner startup for every stack.
- `docker create` had no timeout and held the service's lifecycle gate
while it ran.

## Change

- Readiness is re-checked on demand. Callers share one bounded probe per
launch, each session declares its own probe, and a stale probe can never
mark a newer launch healthy.
- A native launch that exits before becoming ready is relaunched on
fresh ports, but only when its port is actually taken by another
process.
- Saved claims only steer automatic allocation; for fixed ports, the
bind decides. A pre-bind probe runs where the OS lets loopback and
wildcard binds overlap.
- State listings skip unreadable entries and report them. `stop --all`
warns about skipped entries.
- A stack member counts as started only when it is running and healthy.
- `docker create` is bounded, and the named container is removed on
timeout or interrupt.
- Unmarked database data is rejected at startup, and destroy still
removes it.

## Stack

Part 2 of 8 of the stack package simplification, based on #6834. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage ← this PR
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Problem

The owner process had accumulated parallel structures:

- RPC handlers that only forwarded to `Owner`, with errors rewrapped
through three types.
- Seven maps keyed by instance id, and the composition held in two
places.
- Service-specific routes and credential rules inside `Owner.ts`.

Definition changes ran on the caller's fiber. A client disconnecting
mid-`createService` could leave an instance in `state.json` that the
live owner didn't know about, or leak a network namespace so that the
next destroy failed.

## Change

- `Owner` builds the RPC handlers directly, with one error mapping.
`OwnerError`, the mirrored interface and the pass-through handlers are
gone.
- One instance registry and one composition, both owned by the
orchestrator.
- Credential rules move to `host/Credentials` and shared routes to
`host/Endpoints`.
- Definition changes run in the owner scope:
  - A disconnected caller ends only its own wait.
  - Shutdown waits for changes in flight and rejects new ones.
  - Config-changing restarts go through the same gate.
  - A failed creation rolls back credentials it introduced.
- One container sweep path, one snapshot/reset helper, and typed saved
state with unique instance ids.
- RPC error messages are always strings.
- Unused orchestrator operations and RPCs are deleted.

## Stack

Part 3 of 8 of the stack package simplification, based on #6835. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers ← this PR
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

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

## Problem

The snapshot protocol existed twice: in TypeScript for native data, and
in an embedded shell script for Docker volumes. The protocol covers
staging, retiring the old copy, publishing, rollback, retention and the
descriptor check. Every fix had to land in both copies. The shell copy
also depended, unchecked, on binaries from an externally built image.

## Change

- One protocol module owns naming, staging, rollback, retention,
descriptor checks and ready-marker publication.
  - The native engine runs its steps on the host.
- The Docker engine compiles each operation into one helper script, so
each operation is still a single exec.
- Both engines hold a host-side lock and an in-script lock. Retired
generations are recovered, and data and its ready marker are published
atomically.
- A restore creates the data directory's parent, so a fresh Docker
instance can restore before its first start (the warm `db diff` shadow
path).
- Uses of one Docker helper run concurrently, and closes are guarded by
a per-open generation.
- The unreachable recursive fallback copy is deleted.
- One contract suite runs the same scenarios against both engines.
- Runtime session wrapping is shared between process recipes and the
database, and old artifact-cache temp entries are reaped.

## Stack

Part 4 of 8 of the stack package simplification, based on #6836. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines ← this PR
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…try (SDK-1956) (#6822)

## Summary

`gen types` no longer names its languages. `--lang` and the
language-specific flags come from `@supabase/typegen`, the SDK team's
language registry, which maps each language to its generator. Each
registry entry either calls a generator in-process (TypeScript, Go,
Python and Swift, the four bundled in `@supabase/postgrest-typegen`) or
runs the language's own tool in the working directory with the
introspected `GeneratorMetadata` document on stdin. `--lang dart` is the
first out-of-process language: it runs `dart run supabase_typegen
--output -` in the user's project. Adding a language is a pull request
in `supabase/sdk` plus a bump of the one registry dependency here.

### What changed in `apps/cli/src/commands/gen/types/`

- `types.languages.ts` (new): everything derived from the registry in
one place. The `--lang` values, one optional Effect flag per user-facing
option name (merged across the languages that declare it, with the union
of their choices; a name declared with two kinds throws), the
value-taking flag names for the argv scans, the documented defaults
where every declaring language agrees, and `languageOptionValues`, which
forwards only the values the user set so each language applies its own
default. The pure functions take option specs, so the unit tests use
synthetic specs rather than the registry's contents.
- `types.command.ts`: `--lang` is a choice over the registry's names;
the language flags are spread in from `types.languages.ts` after a
collision check against the command's own flags, the global flags and
Effect's built-ins. `--postgrest-v9-compat` is hidden.
- `types.handler.ts`: the mutex groups, the changed-flag check and both
value-flag scans include every registry language flag. `runGenerate`
passes the language options plus the consumer setting
`detect-one-to-one-relationships`, computed as before
(`!(postgrestV9Compat || forcedV9)` on `--local`, `!postgrestV9Compat`
elsewhere). Passing `--postgrest-v9-compat` prints a deprecation line on
stderr before the guards run.
- `types.generator.service.ts`: `GenTypesGenerateInput` carries `lang:
string` and `options: OptionValues`. Two new tagged errors:
`GenTypesToolNotInstalledError` (the registry's message verbatim,
install hint included) and `GenTypesToolFailedError` (the tool's exit
code and stderr in the message, also covering a rejected document
version).
- `types.generator.layer.ts`: opens the `DbConnection` session in its
own scope, introspects in-process and closes the connection, then calls
`findLanguage(lang).generate(metadata, declaredOptions, host)`,
forwarding only the options the language declares. Registry errors map
to the two new errors; anything else stays `GenTypesGenerationError`.
The two tool errors never trigger the IPv4 pooler retry, since the tool
runs after the connection succeeded and its stderr may name a host of
its own. The registry sorts the metadata and returns complete file
contents, so the layer's own sort and trailing newline are gone and
output for the four existing languages is byte-identical. Out-of-process
tools run in the directory the command was invoked from
(`RuntimeInfo.cwd`), not the resolved Supabase workdir, so a Flutter app
inside a monorepo resolves its own `supabase_typegen`.
- `types.typegen-host.ts` (new): the registry `Host` on the Effect
`ChildProcessSpawner`. The generator fiber owns the spawned tool through
`Effect.runPromiseWith(context)` with the request's abort signal; the
child runs in the directory the command was invoked from and inherits
the CLI's whole environment; stdout, stderr and exit code are collected
concurrently, with a signal-ended tool reported as a `null` exit code.
On Windows the command is resolved through `PATH` and `PATHEXT` with the
registry's `resolveWindowsCommand`, and a `.bat` or `.cmd` runs through
the command interpreter with tokens holding whitespace or cmd.exe
metacharacters quoted, since Flutter ships `dart` as `dart.bat`. The
quoting and the script check are the registry's own `quoteForCmd` and
`isWindowsScript` (supabase/sdk#231, in typegen 0.2.0), so this host and
`createNodeHost` build the same line, and a token holding a double quote
or a line break is refused rather than wrapped. A missing command
rejects with `ENOENT`, which the registry turns into its install-hint
error. `format` returns TypeScript unformatted so `oxfmt` stays out of
the binary.
- `SIDE_EFFECTS.md`: registry intro, the out-of-process subprocess row,
two new exit-code rows, and the flag notes.

Elsewhere: `error-actionability.ts` gains the `toolNotInstalled` and
`toolFailed` presets and the error-tag fixture lists the two new tags;
`db-target-flags.ts` and `docs-spec.tables.ts` take the language flags
and their defaults from the registry instead of listing
`swift-access-control` by hand; `tsconfig.types.json` keeps only the
`@supabase/pg-topo` pin.
`patches/@effect__platform-node-shared@4.0.0-rc.112.patch` attaches an
`error` listener to a spawned child's stdin for its lifetime: the
upstream sink listens only while writing and then waits for `finish`
alone, so a tool that exits before reading its input turned the final
flush's `EPIPE` into an uncaught exception that would have crashed the
CLI. The host also feeds stdin from its own fiber rather than handing
the spawner a stream, and `types.typegen-host.integration.test.ts`
drives one kilobyte and four megabytes into a tool that exits at once;
the small write is the one that reaches the unguarded `EPIPE` on Linux.

### Dependency

`@supabase/typegen@0.2.1` from npm, pinned exactly, replaces the direct
`@supabase/postgrest-typegen` dependency. The registry pins
`@supabase/postgrest-typegen@0.3.0`, which compiles under this
workspace's compiler options, so postgrest-typegen is type-checked from
source through the registry like every other `bun`-condition package,
and the `dist` types pin it used to need is gone. Both versions are
excluded from the release-age policy because they were published this
week. A new postgrest-typegen release reaches the CLI through a single
registry bump.

postgrest-typegen 0.3.1 declares `oxfmt` as an optional peer dependency
and loads it only inside its default TypeScript formatter. `gen types`
supplies its own identity formatter, so the compiled binary marks
`oxfmt` external in `compile-options.ts` and ships without it. That one
line replaces the `oxfmtStubPlugin` Bun plugin, the `oxfmt-stub.ts`
module and their use in `scripts/build.ts`, `scripts/build-binary.ts`
and `tools/release/local-release.ts`, all removed here. The binary is
the same size as before, since the stub already kept `oxfmt` out.

### Behavior changes

- `--lang` accepts `dart`. It needs the Dart SDK on `PATH` and
`supabase_typegen` as a dependency of the project the command runs in;
otherwise the error carries the install hint.
- `--swift-access-control` accepts `private` and `package` in addition
to `internal` and `public`, as the generator always did.
- `--postgrest-v9-compat` is deprecated: hidden from help and docs,
still honored with its `--db-url`-only rule, and it prints `Flag
--postgrest-v9-compat has been deprecated, PostgREST 9 reached end of
life; the flag still disables one-to-one relationship detection.` on
stderr.
- The four existing languages produce the same bytes as before against
the databases checked, with one upstream exception: when two schemas
hold an enum of the same name, postgrest-typegen 0.2.2 typed a Python
column as `PublicStatus` and 0.3.0 types it as the schema-qualified
`OtherStatus` and emits that alias, which is the fix for a name
collision. TypeScript, Go and Swift were identical against the same
database.
- TypeScript output ends in one newline instead of two. The old code
appended a newline after every generator regardless of what it produced,
and the TypeScript template already ends in one; since typegen 0.2.1
(supabase/sdk#233, SDK-1961) the registry passes a generator's newline
through instead of adding another. Go, Python and Swift are unaffected:
their generators now emit the newline the CLI used to append.

### Notes for reviewers

- `types.generator.integration.test.ts` runs the real generator layer
against a fake `dart` on a temporary `PATH` through an empty fake
database, covering the invocation directory, stdin delivery, exit codes,
`ENOENT` and the rendered errors; `types.typegen-host.unit.test.ts`
covers the host's result and rejection mapping and the Windows quoting;
the e2e language list derives from the registry. Against a Docker
Postgres with a small schema, `gen types --lang dart --db-url …` run
from a Dart project and `dart run supabase_typegen --db-url …` produced
byte-identical files.
- The Windows path has only run against a fake spawner and filesystem; a
run on a Windows machine with Flutter is still owed. Verified
separately: `dart run supabase_typegen` keeps stdout clean on a cold
`.dart_tool` cache, so no progress output can land in the generated
file.

## Linked issue

No GitHub issue; tracked in Linear as SDK-1956 (maintainer PR).
Follow-up: SDK-1961 moves the trailing newline into the generators, with
no change needed here when it lands.
…tors (#6838)

## Problem

The owner's exclusivity came from binding a sticky control port.

- A foreign listener on that port wedged the stack, including `destroy`.
- The owner never noticed its creator dying, so throwaway stacks (CLI
schema shadows, test stacks) leaked the owner, containers and port
claims after a SIGKILL, crash or OOM.
- Nothing checked that a client and a running owner came from compatible
releases.
- Owner output was discarded.

## Change

- Each owner holds a per-stack SQLite lease for its lifetime. It
publishes an ephemeral loopback endpoint and a per-owner bearer secret
in `owner.json`, with mode 0600.
- Liveness is a no-wait lease probe, so dead stacks cost no network
timeouts.
  - The lease is re-validated against the path it locked.
- The handshake compares a release derived from the stack sources and
the Effect version. Builds and source runs compute the same value.
`stop` and `destroy` go through a release-stable endpoint.
- `lifetime: "session"` binds a stack to its creator through stdin; the
owner destroys the stack when the creator exits. CLI schema shadows use
it.
- The owner sweeps dead owners' containers and session stacks in the
same state root, holding each lease while it does.
- Owner output goes to `owner.log`, and start failures include its tail.
- Clients cache the owner connection per handle and drop it only when
the owner is gone. Calls release their borrows in their own scope, and
waiting for an owner can be interrupted.
- Stack errors carry `owner-unavailable` or `release-mismatch`, so the
CLI only treats a missing owner as "not running".
- The owner exits explicitly once shutdown has flushed its output.
- #6825's guarantees are kept in the new model:
- The owner registers detached stacks under its lease, as it already did
session stacks, and removes the registration if its startup fails. A
failed first launch therefore leaves nothing behind.
- When no owner can start because the container engine is unavailable,
`destroy` removes the stack offline while holding its lease, and reports
the skipped runtime cleanup.

Stacks registered by develop builds from before this PR don't decode
with it, because persisted stack state has no migrations and the package
is unreleased. Destroy them with the build that created them, or remove
`~/.supabase/stacks/<id>` and the containers labelled
`com.supabase.stack=<id>`.

Known gap: if an owner is SIGKILLed right after registering a stack, its
registration is left behind.

## Stack

Part 5 of 8 of the stack package simplification, based on #6837. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators ← this PR
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Problem

The Functions recipe required every caller to pass the bundled
edge-runtime main-service source. The CLI's build, the CLI's stack
runtime and the package's tests each bundled the package's own
`serve.main.ts` with esbuild. The full bundle was also persisted in
every saved Functions creation.

## Change

- Generate the bundle into a committed module with a drift test, wired
into `pnpm generate`. The bundle is self-contained, so it works offline.
- Make the Functions `bootstrap` input optional; the package default is
never persisted.
- Delete the CLI's stack bootstrap bundler, its build define, and the
internal serve-main export. The legacy backend's bootstrap is untouched.

## Stack

Part 6 of 8 of the stack package simplification, based on #6838. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package ←
this PR
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ection (#6859)

`--runtime auto` picked a new stack's runtime from the platform alone:
native on Linux x64/arm64 and macOS arm64, Docker elsewhere. On those
platforms a new stack ran as native processes even when Docker was
running. That gives up the process, filesystem, and network isolation
containers provide, which matters most when several local stacks share a
machine.

For a new stack, auto now selects:

1. Docker, when its daemon answers `docker version`
2. Podman, when its engine answers `podman info`
3. native, on Linux x64/arm64 and macOS arm64
4. otherwise an error asking the user to start Docker or Podman

Having the binary installed isn't enough: a stopped Docker Desktop or
Podman machine falls through to the next option, and each probe gives up
after 10 seconds. Existing stacks keep their saved runtime without
probing, and explicit `--runtime` choices still never fall back.

The same selection applies to `stack start`, `stack prepare`, the
top-level `supabase start` alias when `experimental.stack` is enabled,
project stacks created by database commands, and shadow stacks.
`--runtime` now also accepts `podman`, matching the runtimes stacks
already support and persist.
…ge (#6840)

## Problem

The CLI re-derived policy the package owns:

- It hand-copied the composition's binding table and the eager/lazy
rules.
- It passed placeholder URLs for inputs the composition overwrites.
- It found stacks by listing every stack and matching identity fields.
- It ran database initialisation in four places.

Starting a stopped stack with changed configuration was rejected with a
suggestion to destroy it.

## Change

- `find` reads one stack by identity or id. CLI target resolution uses
it, so an unreadable state file surfaces as an error instead of "not
found".
- Composition-bound inputs are optional. Required inputs are validated
before a service stops, so an invalid restart leaves it running.
Placeholder URLs are gone.
- `composition.plan` reports, per member, whether a requested
configuration is unchanged, changed or incompatible. An `eager` option
replaces the CLI's copy of the activation policy, and `stack status`
reports drift from the plan.
- Starting a stack whose owner is not running applies changed
configuration. Only incompatible changes are rejected: endpoints,
top-level versions and the database major version.
- One stack database initialisation routine is shared by `stack start`,
`db start`, `db reset` and schema shadows.
- The internal artifact entrypoints are merged, the auth mapper's
pass-throughs are type-checked, and the composition `identity` option is
renamed to `keys`.

## Stack

Part 7 of 8 of the stack package simplification, based on #6839. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package ← this PR
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API

---------

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

## TL;DR

`softprops/action-gh-release` uploads every release asset in parallel
with no per-asset retry, so one dropped connection to
`uploads.github.com` fails the whole publish job. Two beta releases
failed this way in September:
[beta.52](https://github.com/supabase/cli/actions/runs/35265023924)
during a multi-hour degradation of the upload backend, and
[beta.60](https://github.com/supabase/cli/actions/runs/35642579188) from
a single dropped connection on an otherwise healthy day.

## What's introduced?

In the `publish` job of `release-shared.yml`:

- The action now creates an **empty draft** release; its `files:` input
is gone.
- A new `apps/cli/scripts/upload-release-assets.ts` defines the asset
list once and exposes two subcommands, each run as its own workflow
step:
- **`upload`** runs `gh release upload --clobber` one asset at a time,
up to three attempts each with a five-minute bound and a short backoff.
`gh` already retries 5xx responses and dropped connections three times
within a second; the outer loop covers longer stalls and the `--clobber`
delete, which `gh` does not retry.
- **`verify`** compares the release's assets in the `uploaded` state
against the expected names and fails before `gh release edit
--draft=false`, so a partial asset set can never be published.
- Unit tests cover the asset list, retry policy, timeout handling, and
verification through an injected runner. An integration test runs the
real spawn path against a fake `gh` on `PATH`, including a hung upload
that must be killed and retried.

The script is plain Bun TypeScript to match its siblings in
`apps/cli/scripts` (`publish.ts`, `sync-versions.ts`, the Homebrew and
Scoop updaters), which the same job already invokes the same way. That
is a deliberate choice against the repo's general Effect rule; the
directory is excluded from Effect lint and has no Effect code today.

`release-process.md` and ADR 0011 are updated to describe the new steps
and the re-run window imposed by build-artifact cache eviction.

The first five commits carried the same logic as inline bash; the last
two replace it with the script. History is kept as is.

## Ref

- closes: CLI-2455
- prior art for the same failure in other repos:
[softprops/action-gh-release#536](softprops/action-gh-release#536),
[OpenwaterHealth/openmotion-bloodflow-app#555](OpenwaterHealth/openmotion-bloodflow-app#555)

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## TL;DR

collapses the effect lint allow list to one `commands/**` entry now that
every command family is covered

## ref

- closes: CLI-2408
@supabase-cli-releaser
supabase-cli-releaser Bot requested a review from a team as a code owner September 29, 2026 02:26
@supabase-cli-releaser supabase-cli-releaser Bot added the do not merge Approve to apply; do not merge. label Sep 29, 2026
avallete and others added 9 commits September 29, 2026 05:30
…6865)

## Summary

**TL;DR:** With the experimental stack (`[experimental].stack = true`),
every Studio page backed by pg-meta failed. That includes the Table
Editor, Auth users, Query Performance, Integrations, and lints. Studio
was never told where Postgres is, so it defaulted to host `db`. Studio
now gets the database connection from the stack. This PR also restores
the Studio settings that the Compose `start` already provides.

### Before
```mermaid
flowchart LR
  B[Browser] --> S[Studio]
  S -->|"connection string<br/>host = db (default)"| M[pg-meta]
  M -->|"ENOTFOUND db"| E["500: Zod formattedError in the UI"]
```

### After
```mermaid
flowchart LR
  B[Browser] --> S[Studio]
  S -->|"connection string<br/>POSTGRES_HOST/PORT from the database binding"| M[pg-meta]
  M --> D[(Stack Postgres)]
```

### Why

Studio builds the encrypted connection string it sends to pg-meta from
`POSTGRES_HOST` / `POSTGRES_PORT` / `POSTGRES_DB` / `POSTGRES_PASSWORD`,
and defaults the host to `db`. The stack set none of these, so pg-meta
logged `getaddrinfo ENOTFOUND db`. The UI then surfaced a Zod parse
error: `"path": ["formattedError"], "message": "Invalid input: expected
string, received undefined"`. The Connect dialog also reported port 5432
instead of the stack's database port.

### What changed

- The composition binds the database `sql` endpoint to Studio (the same
URL pg-meta receives). Studio derives `POSTGRES_HOST`, `POSTGRES_PORT`,
`POSTGRES_DB`, `POSTGRES_PASSWORD`, and
`POSTGRES_USER_READ_WRITE=postgres` from it.
- Studio also receives:
  - `AUTH_JWT_SECRET`
- `PGRST_DB_SCHEMAS` / `PGRST_DB_EXTRA_SEARCH_PATH` /
`PGRST_DB_MAX_ROWS` from `[api]`
  - `NEXT_ANALYTICS_BACKEND_PROVIDER`
  - `CURRENT_CLI_VERSION`
- `SNIPPETS_MANAGEMENT_FOLDER`, backed by a read-write mount of
`supabase/snippets`. `stack start` creates that directory when Studio is
selected, so saved SQL snippets persist in the project.
- S3 protocol keys are not forwarded: the stack's Storage does not
configure S3 credentials yet.

## Linked issue

None.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

**TL;DR:** Every experimental-stack container was named
`supabase-<uuid>` with no service or grouping labels. With lazy wake-up
and idle stops, Docker Desktop and OrbStack showed a stream of anonymous
containers starting and stopping. Containers are now named after the
project and service, and each stack groups as one compose project.

### Before
```mermaid
flowchart LR
  W[Lazy wake] --> N["supabase-3f2a…-uuid<br/>no service or group labels"]
```

### After
```mermaid
flowchart LR
  W[Lazy wake] --> N["supabase-myapp-studio-5f91…"]
  O[Migration step] --> T["supabase-myapp-storage-task-a16a…"]
  N --> G["Grouped as one compose project per stack"]
  T --> G
```

### What changed

- Container names are `supabase-<project>-<service>-<12 hex>`. One-shot
migration and startup containers add a `-task` segment. `<project>` is
the project directory name, plus the stack name when it isn't `default`.
When the project root has no directory name, it is the stack name alone.
Segments are limited to Docker's name alphabet and a bounded length.
- New labels:
- `com.supabase.service=<service>`, the plain service name, so log
collectors can route by it.
- `com.docker.compose.project=supabase-<project>-<stack id prefix>`,
lowercase.
  - `com.docker.compose.service=<service>`.
  - `com.docker.compose.oneoff=True` on one-shot containers.
- Existing `com.supabase.*` labels are unchanged. Cleanup still selects
containers by label, never by name.

## Linked issue

None.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Problem

The package had no supported way to use a stack from tests. Test authors
picked state, cache and project roots themselves, wrote service
creations by hand and nested try/finally teardown, and CLI tests
imported package test helpers by relative path. The Promise API was also
a hand-written copy of the Effect API, and it had already drifted from
it.

## Change

- `@supabase/stack/testing`:
- `createTestStack` works with `await using`, returns typed service
handles, and provides `checkpoint` and `reset`.
  - `makeTestStack` is the scoped Effect variant.
- Test stacks use the session lifetime, eager composition, automatic
ports, and per-user shared roots.
- Checkpoints are instance-scoped snapshots. Parallel stacks cannot
evict them, and destroy removes them. `reset` never wipes data for a
missing checkpoint, and it always brings the composition back.
- The Promise client is derived from the Effect handles, with type-level
parity tests. Only operations that return handles are adapted;
observations and other data pass through unchanged.
- Cleanup:
- The unused `./tools` export and the duplicate default re-exports are
removed.
  - `StackIdentityInput` is now `StackKeysInput`.
  - The default runtime is chosen in the package.
  - CLI tests use a CLI-local cleanup helper.

## Stack

Part 8 of 8 of the stack package simplification, based on #6840. Review
and merge in order:

1. #6834 fix(stack): stop Postgres containers with a fast shutdown
2. #6835 fix(stack): harden readiness, port claims, and container
storage
3. #6836 refactor(stack): flatten the owner process layers
4. #6837 refactor(stack): unify the database snapshot protocol across
engines
5. #6838 feat(stack): lease owners with a lock and bind session stacks
to creators
6. #6839 feat(stack): ship the functions bootstrap with the package
7. #6840 refactor(stack): own composition policy and stack lookup in the
package
8. #6841 feat(stack): add a testing entrypoint and derive the Promise
API ← this PR

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

**TL;DR:** This builds on #6867, which groups a stack's
containers as one compose project in Docker Desktop and OrbStack. The
database storage helper containers (`supabase-db-helper-*`) were still
left outside that group. They now join their stack's compose project as
the `database-helper` service.

Stacked on #6867; merge that first.

### Before
```mermaid
flowchart LR
  G["Compose project supabase-myapp-…"] --> S["studio, rest, database …"]
  H["supabase-db-helper-…"] --> U["Ungrouped in Docker Desktop/OrbStack"]
```

### After
```mermaid
flowchart LR
  G["Compose project supabase-myapp-…"] --> S["studio, rest, database …"]
  G --> H["supabase-db-helper-…<br/>service: database-helper"]
```

### What changed

- A shared `composeProjectFor` in `runtime/ContainerName.ts` derives the
compose project for both the stack's service containers and its helpers.
- The database storage receives the project folder name. Both the
per-instance helper and the shared volume helper get
`com.docker.compose.project` and
`com.docker.compose.service=database-helper`.
- Helper names and existing `com.supabase.*` labels are unchanged.
Helpers carry no `com.supabase.service` label, so service log collectors
skip them.

## Linked issue

None.

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

---------

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

## Summary

**TL;DR:** The experimental stack stopped Studio after 60 seconds
without requests. A Studio tab in the background sends no requests, so
Studio (and the pg-meta and analytics services it depends on) were
stopped almost every time the user switched away. The next click then
had to cold-start all three. Studio now idles after 5 minutes. Every
Studio request still resets the timer. Other lazy services keep 60
seconds.

### Before
```mermaid
flowchart LR
  T["Studio tab in the background<br/>(no requests)"] -->|60s| K["Studio, pg-meta, analytics stopped"]
  K -->|next click| W["~8s cold start of all three"]
```

### After
```mermaid
flowchart LR
  T["Studio tab in the background<br/>(no requests)"] -->|"5 min"| K["Studio, pg-meta, analytics stopped"]
  R["Any Studio request"] -->|resets the 5 min timer| T
```

### Why

Studio's page keeps no connection open, and activity is counted per
proxied request, so the stack can't tell that a browser tab is still
open. A longer idle window for Studio covers the common "switch to the
editor and come back" loop. pg-meta and analytics already can't sleep
while Studio, which depends on them, is running, so they follow Studio's
timer.

### What changed

- The composition assigns Studio a 5-minute idle timeout; the other lazy
services keep 60 seconds and Functions still has none.
- A composition-level test proves that Studio and its pg-meta
prerequisite survive past 60 seconds after the last request, then both
stop once Studio's idle window passes.
- The whole-stack policy scenario and the `experimental stack start`
side-effects doc reflect the Studio timeout.

## Linked issue

None.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

**TL;DR** Running `supabase@beta`'s native stack inside agent sandboxes
hit several first-run failures that were hard to diagnose or avoidable.
Native artifact failures now name every checksum source and mirror
tried. `stack stop` no longer fails when the sandbox never reaps the
exited owner. A missing Docker gets an actionable suggestion.

### Before

```mermaid
flowchart LR
  A[stack start<br/>native] --> C{"checksum:<br/>GitHub, then ghcr"}
  C -->|both blocked| F1["❌ Unable to download<br/>.../SHA256SUMS"]
  C -->|ok| S[services start]
  S --> P[stack stop]
  P -->|owner never reaped| F3["❌ process N is still running"]
  D[stack start<br/>--runtime docker] -->|no docker| F4["❌ spawn docker ENOENT"]
```

### After

```mermaid
flowchart LR
  A[stack start<br/>native] --> C{"checksum:<br/>GitHub, then ghcr"}
  C -->|both blocked| F1["❌ every source with<br/>its HTTP status or errno"]
  C -->|ok| S[services start]
  S --> P[stack stop]
  P -->|owner never reaped| OK["✅ accepted as exited<br/>after the exit wait"]
  D[stack start<br/>--runtime docker] -->|no docker| F4["❌ same error<br/>+ install Docker or<br/>use --runtime native"]
```

### Why

- **Artifact failures dropped their cause.** Only the primary source's
error survived, as `Unable to prepare database artifact: Unable to
download .../SHA256SUMS`, with no HTTP status or network error and no
trace of the ghcr or S3 attempts.
- **`stack stop` failed after a successful shutdown.** The detached
owner is reparented to PID 1. Some sandbox init processes never reap it,
so it stays a zombie, which still answers `kill(pid, 0)`. The stop then
failed with `Owner shutdown acknowledgement completed, but process N is
still running`.
- **Root sandboxes without a detected provider** failed with `PostgreSQL
cannot be run as root`, with no hint on how to proceed when no non-root
account exists.
- **Missing Docker** surfaced as a raw `spawn docker` error, although
the stack already classifies it as `runtime-unavailable`.

### What changed

- When every checksum source or archive mirror fails, the error lists
each one with its full failure chain (HTTP status or error code). It
keeps the artifact's service and version. ADR 0026 is updated for this.
A shared `errorChainMessage` (moved out of `Orchestrator.causeMessage`)
unwraps nested causes. The S3 bucket remains an archive mirror only, not
a checksum authority.
- The owner-exit probe takes `FileSystem` (`ownerExitProbe(fs)`) and, on
Linux, reports a `Z`/`X` owner as a zombie. The exit wait keeps retrying
while the owner is a zombie, so a reaping parent can clear it. An owner
still unreaped when the wait ends is accepted as exited.
- A Modal Sandbox (`MODAL_SANDBOX_ID` present) triggers the existing
non-root PostgreSQL account selection. The root-refusal suggestion now
shows how to create an account.
- `StackError.reason` gains `runtime-unavailable`, set when the
container engine is unreachable. `stack start` uses it to suggest
installing or starting Docker. It adds `--runtime native` only when
creating a new stack on a platform that supports native.
- Updated `stack start` `SIDE_EFFECTS.md`, `packages/stack/README.md`,
`docs/stack-commands.md`, and ADR 0026.

## Linked issue

No linked issue; found while running the beta CLI in agent sandboxes.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

The root help header is hard-coded to `Supabase CLI (stable channel).`,
so beta releases, PR preview packages, and source runs all claim to be
stable.

- Stable releases now print `Supabase CLI.` with no label.
- Other builds name what they are, derived from the prerelease
identifier in `CLI_VERSION`: `-beta.N` prints `Supabase CLI (beta
channel).`, `-pr.N` prints `Supabase CLI (preview build).`, and every
other prerelease (including `-dev` and `-automated`) prints `Supabase
CLI (development build).`
- The semver parser moves from the upgrade notice into the shared
version module so the header and the notice agree on what a prerelease
is. `--version` output is unchanged.

A survey of popular developer CLIs found none that labels stable builds
by channel in the help header, and only one that labels prereleases
there. Details and alternatives considered are in CLI-2523.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…root (#6878)

## Problem

`StackHost.integration.test.ts` › "serves one detached owner through
Effect RPC and retires after shutdown" fails intermittently with
`NotFound: FileSystem.makeTempDirectoryScoped` on the test root.

The owner acknowledges `/shutdown` before it finishes winding down. It
then removes `owner.json`, and its lease finalizer removes `owner.lock`
and the stack directory. The test's scoped temp directory is removed
while those unlinks are still running. Bun's recursive `fs.rm` reports
any entry that vanishes mid-delete as `ENOENT` on the root. The
lease-file unlink at owner exit made this window common.

## Change

The test waits for the owner process to exit after its shutdown
assertions, and the failure-path finalizer does the same.
`StackHost.container-shutdown.integration.test.ts` already follows this
pattern, and the product's own stop already waits for exit.
<!--
Before opening this PR, confirm the linked issue is open and carries the
`open-for-contribution` label. PRs from external contributors that don't
follow
the workflow in CONTRIBUTING.md are closed automatically.
@supabase members working from Linear tickets are exempt.
-->

## Summary

- bumps bun (`v1.4.1` ➡️ `v1.4.2`)
- bumps `@types/bun` accordingly, which allows us to clean up our shared
compilation options
- bumps pnpm (`v12.4.0` ➡️ `v12.8.1`)
- ~~enables the new
[`autoDedupe`](https://pnpm.io/settings/dependency-resolution#autodedupe)
option, which allows us to clean up a bit of config (**edit**: jk, this
doesn't actually work 😔)~~
- bumps node.js (`v24.20.0` ➡️ `v24.21.0`)
- bumps our mise/pnpm lockfiles accordingly
- small cleanups in our `turbo.json` cache inputs (`mise.lock` makes it
unnecessary to include `.bun-version` and `mise.toml` in our task
inputs)
- points to `.bun-version` in our stack tests

---------

Co-authored-by: Julien Goux <hi@jgoux.dev>
avallete and others added 7 commits September 30, 2026 12:19
…6903)

## Summary

**TL;DR:** `supabase stack destroy --yes` printed its confirmation as a
self-answered question (`… owned data? … [y/N] y`). It now prints a
plain statement of what is being destroyed and that Storage uploads are
preserved. The interactive prompt is unchanged.

### Before

```mermaid
flowchart LR
  A["stack destroy --yes"] --> B["stderr: Permanently destroy stack … owned data? … [y/N] y"] --> C[destroy]
```

### After

```mermaid
flowchart LR
  A["stack destroy --yes"] --> B["stderr: Permanently destroying stack … and its owned data. Storage upload files will be preserved."] --> C[destroy]
  D["stack destroy (interactive TTY)"] --> E["prompt: Permanently destroy stack …? [y/N]"] --> C
```

### Why

In non-TTY output (CI logs, agent transcripts) the `[y/N] y` line looks
like a pending prompt that someone answered, even though nothing was
asked. #6889 removed the same pattern from bucket seeding
during `stack start`.

### What changed

- With `--yes`, `stack destroy` writes `Permanently destroying stack
<id> at <root> and its owned data. Storage upload files will be
preserved.` to stderr instead of calling the prompt helper.
- Without `--yes`, an interactive text TTY still sees the same `[y/N]`
question. Non-interactive or machine-output runs without `--yes` still
fail with the existing "rerun with --yes" error.
- The shared `promptYesNo` helper is untouched, so other commands keep
their current `--yes` output.
- `SIDE_EFFECTS.md` and the handler integration tests cover the new
statement and the interactive decline path.

## Linked issue

None.

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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ws (#6905)

## Summary

**TL;DR:** Four `apps/cli` integration tests fail on Windows even on
`develop`, but CI stays green because the Windows job only runs part of
the `packages/stack` suite. This PR fixes the tests so they pass on
Windows and still cover exactly what they covered on Linux and macOS. It
also adds both CLI test files to the Windows CI job. No product code
changes.

### Why

- `db reset` `schema_paths` permission tests: they use `chmod 0o000` to
make a schema file unreadable or a directory unlistable. Windows ignores
POSIX modes, so the reset succeeds and both tests fail.
- `db reset` "seeds an absolute --sql-paths file": the CLI already
prints seed paths with forward slashes on every platform
(`C:/…/external-seed.sql`). The test expected the backslash form.
- `experimental stack start` "…owner cannot reach Docker": the fake
`docker` is a `#!/bin/sh` script, added to PATH with `:`. Windows splits
PATH on `;` and only resolves `docker` to `docker.exe`, so the stack
owner used the real Docker Desktop, started a real stack, and hit the 5
s test timeout.

### What changed

- **Permission tests:** still use a real `chmod` wherever POSIX modes
are enforced. On Windows, or when running as root (where the tests used
to be skipped), they inject a `PermissionDenied` filesystem failure
instead.
- **Absolute seed test:** expects the forward-slashed absolute path the
CLI prints. This changes nothing on POSIX.
- **Stack start:** the original daemon-refusal test is kept unchanged
except for a new name and a skip on Windows. A new test sets an empty
PATH, so no Docker CLI can be found, and checks the same outcome on
every platform: `runtime` error, Docker suggestion, and no stack left
registered.
- **CI:** the `test-stack-ports` job gets a Windows-only step that runs
`reset.integration.test.ts` and `start.integration.test.ts`.

### Reviewer notes

- These changes were checked on macOS only. The new Windows CI step is
the first real Windows run.
- The GitHub `windows-latest` runner has no Docker Desktop. A regression
that lets the real `docker.exe` back into the no-CLI test will therefore
only show up on a machine with Docker installed.
- Other tests use the same POSIX-only setup and would also fail on
Windows: both `destroy.runtime-unavailable` integration tests and the
`chmod` cases in `sql-files-glob.unit.test.ts`. They are out of scope
here.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary

**TL;DR:** Under the experimental stack backend, `supabase db pull
--db-url "$DB_URL"` with the `DB_URL` printed by `supabase stack status
--env` failed with a TLS error unless `sslmode=disable` was appended.
The CLI now recognizes that URL as the local stack database and connects
in plaintext, like `--local`. Remote URLs keep requiring TLS.

### Before

```mermaid
flowchart LR
  A["--db-url from<br/>stack status --env"] --> B{"port = [db].port<br/>or shadow_port?"}
  B -- yes --> L["local: plaintext"]
  B -- "no (stack port)" --> R["remote: TLS only"]
  R --> F["tls error: server does<br/>not support SSL"]
```

### After

```mermaid
flowchart LR
  A["--db-url"] --> B{"port = [db].port<br/>or shadow_port?"}
  B -- yes --> L["local: plaintext"]
  B -- no --> C{"stack backend and<br/>host:port = running<br/>stack SQL endpoint?"}
  C -- yes --> L
  C -- "no / lookup fails" --> R["remote: TLS only"]
```

### Why

```
failed to connect to postgres: ... tls error (The server does not support SSL connections)
```

The `--db-url` resolver classified a URL as local only when its port
matched `[db].port` / `[db].shadow_port`. The stack backend publishes
Postgres on a runtime-assigned port, so the stack's own URL was
classified remote, and the driver layer makes an unset/`prefer`
`sslmode` TLS-only for remote targets (no TLS-to-plaintext downgrade).
`db dump` was unaffected because `pg_dump`'s libpq `prefer` falls back
to plaintext; `db pull`, `gen types`, `inspect`, and other driver-based
commands all hit the error.

### What changed

- Under the stack backend, a `--db-url` whose host and port equal the
running project stack's SQL endpoint is a local target (plaintext, no
role step-down, local connect hints), matching what `db dump` and `test
db` already expect for stack URLs.
- A passwordless stack URL is filled from the stack's saved database
password rather than `[db].password`.
- If the stack is not registered, not running, or the lookup fails, the
target stays remote; an explicit `sslmode` (e.g. `require`) is still
honored.
- The db pull `SIDE_EFFECTS.md` documents the classification.

Probe-and-fallback for loopback hosts and emitting `sslmode=disable`
from `stack status --env` were considered and rejected: the first
relaxes the no-downgrade policy for every command, the second leaves
hand-typed stack URLs broken.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## TL;DR

With `[experimental] stack = true`, the function generated by `supabase
functions new` failed to boot and answered `503 BOOT_ERROR`. The stack
handed the function's own `deno.json` to Edge Runtime as a plain import
map, which does not expand `jsr:` subpaths. It now lets Edge Runtime
load that file as the function's Deno config, matching `functions serve`
without the stack.

## Before

```mermaid
flowchart LR
  A["functions/hello/deno.json"] --> B["context.importMapPath"]
  B --> C["plain import map<br/>no jsr: subpaths, no comments"]
  C --> D["503 BOOT_ERROR"]
```

## After

```mermaid
flowchart LR
  A["functions/hello/deno.json"] --> B{"nearest Deno config<br/>of the entrypoint?"}
  B -->|yes| C["omitted, Edge Runtime<br/>loads it as Deno config"]
  C --> D["200"]
  B -->|no, e.g. import_map.json| E["context.importMapPath<br/>as before"]
```

| Function | Before | After |
| --- | --- | --- |
| `functions new` template (`import_map =
"./functions/hello/deno.json"`) | 503 `BOOT_ERROR` | 200 |
| template with the `import_map` line removed (auto-detected
`deno.json`) | 503 `BOOT_ERROR` | 200 |
| `deno.jsonc` containing a comment | 503 `BOOT_ERROR` | 200 |
| plain `import_map.json` | 200 | 200 |
| no config file | 200 | 200 |

Same on the docker and native runtimes.

## Why

Edge Runtime log for the template:

```
worker boot error: failed to bootstrap runtime: failed to create the graph: Relative import path "@supabase/functions-js/edge-runtime.d.ts" not prefixed with / or ./ or ../ and not in import map
```

Edge Runtime reads `context.importMapPath` with a plain JSON import-map
loader. That path skips Deno config handling: the `jsr:`/`npm:` subpath
expansion, JSONC comments, and workspace import merging. When the option
is omitted, Deno discovers the nearest `deno.json`/`deno.jsonc` from the
entrypoint and loads it as a config file.

## What changed

- The Functions bootstrap passes an import map only when Edge Runtime
would not already discover it as the entrypoint's nearest regular
`deno.json`/`deno.jsonc` inside the project.
- Custom import maps (`import_map.json`, or a config other than the
nearest one) are still passed explicitly, so they keep overriding
discovery.
- A configured `deno.json(c)` that is not the nearest config (for
example `functions/_shared/deno.jsonc`) is still loaded as a plain
import map, which drops comments and `jsr:`/`npm:` subpath imports. The
bootstrap now logs a one-time warning per function for that case.
- Regression coverage: resolver integration cases, an Edge Runtime boot
test, and a `functions new` → `stack start` → request E2E on docker and
native.

## Linked issue

None.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## TL;DR

Several rough edges show up when the experimental `stack` backend runs
in scripts, worktrees, or on Windows: spinner escape codes in non-TTY
logs, a self-answered bucket prompt, a wrong branch name after `db
reset`, a leftover stack after an unsupported native start, and
top-level help that describes `stack` subcommands. This PR fixes each
one.

## Before

```mermaid
flowchart LR
  A["stack start --runtime native<br/>on win32"] --> B["Stack created"] --> C["Artifact check fails"] --> D["Stopped stack left behind"]
```

## After

```mermaid
flowchart LR
  A["stack start --runtime native<br/>on win32"] --> B["Runtime preflight fails"] --> C["No stack created"]
```

## Why

- Non-TTY output (CI, agents, `--agent no`) was flooded with spinner
escape codes.
- `stack start` printed `Bucket X already exists. Do you want to
overwrite its properties? [Y/n]` and answered it itself.
- `db reset` in a detached-HEAD worktree printed `Finished supabase db
reset on branch main.`; in a nested worktree it could report the parent
checkout's branch.
- `stack start --runtime native` on win32 created a stack, failed with
`Native artifacts are unsupported on win32/x64`, and left a stopped
record that needed `stack destroy`.
- `supabase start --help` showed `stack start`'s description and
`supabase stack start` examples.

## What changed

- The animated spinner runs only when stdout is a TTY; other output gets
plain progress lines for each task and its updates.
- `stack start` seeds existing buckets without prompting. `db reset`
prompt behavior is unchanged.
- Git branch detection follows `.git` gitlink files, stops at the
nearest `.git`, and returns no branch for a detached HEAD; `db reset`
and `db diff` omit the branch clause instead of assuming `main`.
- An explicit `--runtime native` is rejected before a stack is created
when the platform has no native artifacts. The error is classified as a
flags problem rather than Docker not running, and suggests `--runtime
docker` or `--runtime podman` (destroying an existing native stack
first).
- The top-level `start`, `status`, and `stop` aliases have their own
descriptions and examples.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ainers (#6887)

## TL;DR

Three experimental `stack` backend bugs block Windows and Docker Desktop
users: every Edge Function returns 404 on Windows, Realtime
`postgres_changes` never connect on Docker Desktop, and `db dump
--db-url` cannot use the `DB_URL` that `stack status` prints. This PR
makes container paths POSIX, gives stack containers an IPv4-only host
alias, and rewrites loopback database URLs for tool containers.

## Before

```mermaid
flowchart LR
  A["Realtime tenant connection"] -->|"resolves host.docker.internal"| B["IPv6 address first"]
  B -->|refused| C["Host listener<br/>IPv4 only"]
  D["Windows Functions root"] --> E["\__supabase_project\..."] --> F["404 Function not found"]
  G["db dump --db-url 127.0.0.1"] --> H["pg_dump container loopback"] --> I["Connection refused"]
```

## After

```mermaid
flowchart LR
  A["Realtime tenant connection"] -->|"resolves host.supabase.internal"| B["IPv4 host gateway"]
  B --> C["Host listener<br/>IPv4 only"]
  D["Windows Functions root"] --> E["/__supabase_project/..."] --> F["Function served"]
  G["db dump --db-url 127.0.0.1"] --> H["rewritten to host.docker.internal"] --> I["Host-published stack port"]
```

## Why

- On Windows the Edge Runtime receives
`SUPABASE_INTERNAL_FUNCTIONS_ROOT=\__supabase_project\supabase\functions`;
the bootstrap only accepts `/`-rooted paths, so every function answers
`404 Function not found`.
- On Docker Desktop, `host.docker.internal` resolves to IPv6 first
inside containers while the stack host listener is IPv4-only.
`DB_IP_VERSION=ipv4` only covers Realtime's own repo; tenant CDC
connections probe IPv6 first, so channel joins fail with
`UnableToConnectToProject`.
- `db dump --db-url "postgresql://…@127.0.0.1:<stack port>/postgres"`
runs `pg_dump` in a container where `127.0.0.1` is the container itself.
The loopback rewrite only applied when the port matched `config.toml`,
which dynamic stack ports never do.

## What changed

- Edge Functions container paths (functions root, files root,
entrypoints, import maps, static files) are built as POSIX paths
regardless of the host OS.
- Docker stack containers get a `host.supabase.internal` alias, used as
the Docker runtime address for service-to-service URLs. Docker Desktop
maps `host-gateway` to both IPv4 and IPv6, so each stack host probes the
engine's IPv4 host gateway once and maps the alias to it explicitly. The
probe runs in the background while the database starts (the database
keeps `host-gateway`, since it never needs the IPv4-only alias); other
services wait for the shared result, retry a failed probe once, and fall
back to `host-gateway` when no IPv4 is found. Engines that reject
`host-gateway` in `--add-host` (such as older Podman behind the Docker
socket) get the IPv4 they map to
`host.docker.internal`/`host.containers.internal`, or an actionable
error when there is none. Linux keeps the existing
`host.docker.internal` mapping; Podman and native runtimes are
unchanged.
- `db dump` rewrites loopback `--db-url` targets for non-managed stack
targets and for tool containers on a named network. Managed `--local`
stack dumps are unchanged.
- The same loopback rewrite applies to the `db diff` migra and pgAdmin
containers and to the `db pull` initial schema dump.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Graduate OrioleDB local setup out of experimental for the OrioleDB
public beta.

- `supabase init --use-orioledb` no longer requires `--experimental`.
- The OrioleDB version setting moves from
`experimental.orioledb_version` to `db.orioledb_version`, and its
environment override is now `SUPABASE_DB_ORIOLEDB_VERSION`.
- Existing configs that set `[experimental] orioledb_version` keep
working: the value is used when `db.orioledb_version` is unset or empty,
and the CLI prints a deprecation warning.
- `supabase init --use-orioledb` now writes the current OrioleDB
Postgres 17 release (`17.11.0.002`) instead of a Postgres 15 alpha
version that did not match the default `db.major_version = 17`.
- `@supabase/config` adds `db.orioledb_version` and marks
`experimental.orioledb_version` as deprecated.

Companion docs PR: supabase/supabase#50908.
…LI-2546) (#6898)

## Summary

With the stack backend, the local MCP server is not reachable at the API
URL: the shared API listener has no `/mcp` route, so MCP is only served
on Studio's own port under its internal `/api/mcp` path. The legacy
stack exposes it at `<API_URL>/mcp` (Kong maps `/mcp` to Studio's
`/api/mcp`), which is the documented local address,
`http://127.0.0.1:54321/mcp` with the default configuration.

- Studio's HTTP endpoint contributes a join-only `/mcp` → `/api/mcp`
route to the shared API listener. It never claims or asserts the API
port, is queued until a claiming endpoint binds, and is removed when
Studio's namespace closes or is released.
- The route is derived when Studio registers, so nothing is persisted
and existing stacks gain it on their next start. A request to `/mcp`
wakes a sleeping Studio.
- Exact-prefix requests now map to the upstream prefix itself instead of
gaining a trailing slash (`/mcp` → `/api/mcp`), matching how the legacy
gateway strips route paths.

A follow-up to #6894 will report `MCP_URL` as `<API_URL>/mcp`.

---------

Co-authored-by: Julien Goux <Julien@supabase.io>
Comment thread packages/stack/src/HttpProxy.integration.test.ts Dismissed
avallete and others added 3 commits September 30, 2026 14:01
## Summary

**TL;DR:** The CLI now bundles `@supabase/pg-delta@1.0.0-alpha.56` and
`@supabase/pg-topo@1.0.0-alpha.7`. `db diff`, `db pull`, and declarative
sync pick up the engine fixes from alpha.53 through alpha.56. A
partition-key type change drops and recreates the table, because
Postgres rejects an in-place `ALTER` on a key column.

### Before

```mermaid
flowchart LR
  cmd["db diff / pull / declarative sync"] --> old["pg-delta alpha.52"]
  old --> fail["enum cast, grants, pgmq SCHEMA, range order, and partition-key ALTER fail or churn"]
```

### After

```mermaid
flowchart LR
  cmd["db diff / pull / declarative sync"] --> new["pg-delta alpha.56"]
  new --> ok["those plans converge"]
  new --> drop["partition-key change drops the table"]
```

## Why

`develop` was still on `1.0.0-alpha.52`. The published alphas since then
fix enum retypes through `text`, domain `NOT NULL` on PostgreSQL 17,
column and identity-sequence grants, `pgmq` without a redundant `SCHEMA`
clause, range-type ordering, and partition-key changes that previously
failed apply. Dogfood on a linked staging project converged for push, an
empty linked diff, a one-column pull, and declarative generate then
sync.

## What changed

- Bump `@supabase/pg-delta` from `1.0.0-alpha.52` to `1.0.0-alpha.56`.
- Bump peer `@supabase/pg-topo` from `1.0.0-alpha.6` to `1.0.0-alpha.7`,
which alpha.56 requires.
- Point `minimumReleaseAgeExclude` at those two versions.

## Linked issue

Supabase maintainer change; no public issue to close.
…t lint (CLI-2559) (#6911)

## TL;DR

brings the `/shared` init, feedback and services areas under the effect
lint

## whats introduced?

effect lint applied to the three areas and their tests:

- `shared/init/**`, `shared/feedback/**` and `shared/services/**` allow
list entries, with the generated `feedback/database.types.ts` kept out
- `slimImagesEnabled` reads `SUPABASE_USE_SLIM_IMAGES` through `Config`
from the process environment instead of `process.env`, so a project
value or a failing provider still cannot change it
- the slim helpers, the `start` container specs and the image resolvers
take the flag as an argument instead of reading it themselves, and every
caller resolves the same images as before
- `project-init` writes the merged IDE settings through a schema encoder
with the same two space output
- `services.shared` yields `ServiceVersionNotFoundError` directly
instead of wrapping it in `Effect.fail`
- tests use scoped temp dirs, `FileSystem` and scoped servers instead of
`node:fs` and async helpers, and pass the slim flag explicitly

## ref:

- closes: CLI-2559
## TL;DR

stops `config diff` from reporting `auth.sms.<provider>.enabled` on
projects that never set up sms.

## whats broken?

`sms_provider` reads `twilio` on projects that never set up sms, and the
diff took it as an enabled provider.
every such project reported `auth.sms.twilio.enabled` and no
`config.toml` could clear it.

## now fixed by:

a provider whose identity attribute (like the twilio account sid) is
explicitly unset now counts as disabled
projects that never set up sms diff clean, and configured providers are
reported as before, a provider declared locally shows as disabled
against such a project until it is pushed..

## ref:

- closes: #6680
- broken in: #6339
7ttp and others added 13 commits September 30, 2026 14:52
…6906)

## TL;DR

fixes the sql splitter cutting `E'...'` strings in two at a `;` that
follows a backslash escaped quote.

## whats broken?

the splitter only treated a doubled quote as staying inside a string,
so in `select E'it\'s; here';` the `\'` closed the literal and the `;`
split the statement.
migrations and seeds go thru this splitter, and postgres rejects the
first half `select E'it\'s` with `unterminated quoted string`..

## now fixed by:

treating `'` as the start of an escape string when the `E` or `e` before
it starts a token, and skipping the character after each backslash
inside it.

an escape string continued on a following line, `E'first'` then
`'second\'; third'`, now stays one literal the way the postgres server
reads it. plain strings, `type'a\'`, `U&'a\'` and quoted identifiers
keep the backslash literal as before.

## ref:

- closes: #6885
## TL;DR

brings the `output` area under the effect lint

## whats introduced?

effect lint applied to the output layers, the output service and their
tests:

- `shared/output/**` allow list entry
- the delayed task spinner is a fiber owned by the text layer instead of
a `setTimeout`, so a task still pending when the layer closes no longer
shows a spinner afterwards
- `stream-json` events take their timestamp from the effect `Clock` when
the line is written, same format as before
- flagged `JSON.stringify` writes go through a `Schema` json encoder,
same bytes out, and an unserializable payload still dies as a
`TypeError`
- `OutputTask.clear` is a plain effect instead of a zero arg function,
so callers and mocks use `task.clear`
- tests use `TestClock` instead of fake timers, a schema decoder instead
of `JSON.parse`, and pin the exact `stream-json` lines

## ref:

- closes: CLI-2561
… in JSON (CLI-2546) (#6914)

## Summary

Follow-up to #6894 and #6898 (which serves Studio's MCP server at `/mcp`
on the shared API listener). With the stack backend, `start` and
`status` now report connection details the way legacy `start`/`status`
do:

- `MCP_URL` is `<API_URL>/mcp` (for example
`http://127.0.0.1:54321/mcp`), present when the API listener and a
Studio member exist. `endpoints` no longer carries a synthetic
`studio.mcp` entry.
- `DB_URL` is a plain
`postgresql://postgres:<password>@<host>:<port>/postgres` using the
`postgres` role, built by the same helper `db` commands connect with,
without the internal `connect_timeout` parameter.
- The exported variables are `API_URL`, `REST_URL`, `FUNCTIONS_URL`,
`DB_URL`, `STUDIO_URL`, `MCP_URL`, `MAILPIT_URL`, `INBUCKET_URL` (a
deprecated alias of `MAILPIT_URL`, as in legacy), `PUBLISHABLE_KEY`,
`SECRET_KEY`, `ANON_KEY` and `SERVICE_ROLE_KEY`. The never-populated
`S3_PROTOCOL_*` names are no longer accepted by `--override-name`, which
now names a rejected entry and lists the valid variables.
- The `status --env` hint printed by `start` is exercised through the
real command grammar, including named stacks and `--workdir`.

**Deliberate contract change:** `start` JSON (every success path) and
`status` JSON now include an `env` object with those values, including
the local keys and the password-bearing `DB_URL`. It is the same map
`status --env --output-format json` returns. Previously the docs
promised that only `status --env` revealed credentials; this matches
legacy `start`/`status`, whose JSON output includes the local keys.
Plain `status` JSON `env` comes from saved bindings and still reports
when credentials cannot be read, while `--env` requires a reachable
owner and a running primary database.
## TL;DR

stops a piped `db push` from applying migrations when the confirmation
answer is not a yes or a no.

## what's biting the user?

with stdin piped, `promptYesNo` fell back to the default on any line it
could not parse. `db push` defaults to yes, so an answer like `u` or
`nope` applied the migrations

## now fixed by:

declining any non empty answer that is not `y`, `yes`, `n` or `no` once
the prompt was asked, which departs from the Go `PromptYesNo` on
purpose. stdin `readLine` no longer trims, so a line of only spaces
declines too. an empty line or EOF still takes the default

`migrationConfirm` follows the same rule, so `migration fetch` and
`migration squash` no longer proceed on a typo

the real TTY selector, `--yes`, machine output and `interactive: false`
reads are unchanged

## ref:

- closes: #6869
… (#6790)

## TL;DR

stops `supabase test new` from writing files outside `supabase/tests`
when the name contains `..`.

## prob

the name was joined onto `supabase/tests` with no containment check, so
`test new ../../../escaped` wrote `escaped_test.sql` outside the project
and exited 0.

PS: `migration new` and `functions new` already reject names like this.

## sol

the target is now checked against `supabase/tests`
before anything is written, failing with `invalid test name` and counted
as invalid input in telemetry.
subdirectory names like `sub/foo` keep working. only names that escape
now exit 1 where they used to succeed...

## ref

- closes: #6742
## Summary

The stack starts Mailpit without a database path, so Mailpit uses a
temporary SQLite file named from the current time under the system temp
directory and deletes it on every stop. Two native stacks waking mail
together could end up on the same file, and one Mailpit exited with
`database is locked (5) (SQLITE_BUSY)`; Auth could then not wake and
`/signup` returned 502 in the native parallel-stacks e2e. Because Mail
is a lazy member with an idle stop, captured emails also disappeared
after a minute without traffic.

- Process-recipe services can opt in to an owned instance directory
(`<stack data root>/<instance id>`), claimed and removed through the
same ownership marker logic the Database service uses, now shared in
`services/InstanceRoot.ts`. Natively the recipe receives the host path;
in containers the directory is mounted at `/instance`.
- Mail opts in and sets `MP_DATABASE` to `mailpit.db` in that directory,
so each instance has its own database, emails survive idle sleep and
stop/start, and destroying the instance or stack removes them.

---------

Co-authored-by: Julien Goux <Julien@supabase.io>
…6909)

## Summary

When a lazy member's wake stalled behind a TCP endpoint, a client saw an
accepted connection that never answered until its own timeout fired, and
whole-stack e2e failure diagnostics only showed child-service output, so
nothing said where the wake stopped. This surfaced as a rare `PgClient:
Connection timed out` against the pooler after a stack reopen in the
native lifecycle e2e.

- The orchestrator logs when a wake is requested, with the service and
trigger, and when the member is ready, started without awaiting
readiness, or fails to wake.
- The TCP proxy logs an error naming the endpoint when waking or
connecting to its target fails. Transport resets after a successful
connect are not logged as failures.
- Whole-stack failure diagnostics include the tail of the stack owner's
log.

The proxies still wait for a wake without a bound of their own: a wake
includes preparation (image pulls) and prerequisite startup, which
already have their own budgets, and a single proxy-side timeout could
interrupt a legitimate cold start.
## TL;DR

pg-delta shadow databases no longer run `CREATE DATABASE
contrib_regression TEMPLATE postgres`. That clone crashes OrioleDB when
the platform baseline contains an enum-indexed OrioleDB table, and the
crash takes down the whole shadow. Migra still creates the database,
because it uses it as the declarative diff target.

## Before

```mermaid
flowchart LR
  shadow["pg-delta shadow"] --> clone["CREATE DATABASE TEMPLATE"]
  clone --> crash["OrioleDB crash"]
```

## After

```mermaid
flowchart LR
  next["pg-delta shadow"] --> pg["postgres only"]
  migra["migra shadow"] --> clone["contrib_regression"]
```

## Why

On an OrioleDB image, `CREATE DATABASE … TEMPLATE` segfaults when the
template has an OrioleDB index on an enum column. A fresh Supabase
database has one: `auth.one_time_tokens_user_id_token_type_key`. Shadow
setup then fails with a connection error. pg-delta never connects to
`contrib_regression`.

## What changed

- The pg-delta declarative shadow applies the platform baseline and does
not clone `contrib_regression`. A warm cache hit does not connect just
to create it.
- The pg-delta migrations shadow applies migrations on `postgres` and
skips the clone.
- The migra shadow still creates `contrib_regression`.
…6921)

## Summary

On Docker Desktop for macOS, every upload to a stack's Storage on the
Docker runtime failed with 500 `ENOTSUP`. Storage's file backend keeps
object metadata in extended attributes, and Docker Desktop's file
sharing drops them on bind mounts. This pins the stack's Storage to slim
`v1.79.28-r1`, whose `fs-xattr` wrapper falls back to sidecar metadata
files when xattrs are rejected. The bind mount at
`supabase/.temp/stack-uploads/<stack-id>` stays as it is. This replaces
the engine-volume workaround in #6908.

### Before

```mermaid
flowchart LR
  A["Upload to stack Storage<br/>(Docker Desktop macOS)"] --> B["storage v1.73.0<br/>fs-xattr setAttributeSync"]
  B -->|ENOTSUP on bind mount| C["500 on every upload"]
```

### After

```mermaid
flowchart LR
  A["Upload to stack Storage<br/>(Docker Desktop macOS)"] --> B["storage v1.79.28-r1<br/>fs-xattr wrapper"]
  B -->|xattrs work| C["native attributes"]
  B -->|ENOTSUP| D["sidecar under<br/>stack-uploads/.slim-xattr"]
```

### What changed

- **Storage pin:** `packages/stack/src/Artifacts.ts` moves from
`v1.73.0` to `v1.79.28-r1`, pinned by digest. This is the first catalog
pin to a revisioned slim release, and the release tag, asset names,
native OCI tags and image tag all follow the `v1.79.28-r1` form the
catalog already derives.
- **Fix location:** the fix lives in the slim build, in
supabase/slim-services#329 and supabase/slim-services#330.
- Native xattrs stay authoritative wherever they work: Linux, OrbStack
and Windows Docker Desktop.
- On `ENOTSUP`, metadata goes to `.slim-xattr/<2 hex>/<sha256>.json`
under the uploads directory.
- A throttled background sweep removes sidecars for deleted objects
without blocking requests.
- **`supabase services`:** in stack mode it reports catalog versions
without the slim revision suffix. A linked project on the same upstream
release, for example `v1.79.28`, therefore doesn't show a version
mismatch. `SIDE_EFFECTS.md` is updated to match.

### Reviewer notes

- The Storage version jump from `v1.73.0` to `v1.79.28` was compared on
Docker Desktop against the pinned image: status codes and headers
matched across a broad set of Storage API scenarios, both on native
volumes and through the sidecar.
- Known limitations of the fallback are recorded in slim-services
`services/storage/REPORT.md`.
- An object uploaded where xattrs work, then read under Docker Desktop,
is served as `application/octet-stream`.
- A multipart part rewritten across two engine switches within one
upload keeps a stale etag.
- The legacy `supabase start` Dockerfile pins
(`supabase/storage-api:v1.77.0`) are unchanged. It mounts Storage from a
named volume, so it isn't affected.

## Linked issue

Closes #6900

- [x] The linked issue is **open** and carries the
`open-for-contribution` label (or I'm a Supabase maintainer).

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

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

## Summary

With the experimental stack, Storage behind the gateway at
`<API_URL>/storage/v1` was missing configuration that the legacy
`supabase start` path sets. This broke S3 clients, resumable (TUS)
uploads, and therefore Studio file uploads, on both the native and
Docker runtimes.

**Storage recipe (`packages/stack/src/services/Storage.ts`)**

- `S3_PROTOCOL_PREFIX=/storage/v1`: the gateway strips the prefix and
sends no `x-forwarded-prefix`, so Storage verified SigV4 signatures
against a different canonical path (`SignatureDoesNotMatch`).
- `S3_PROTOCOL_ACCESS_KEY_ID`, `S3_PROTOCOL_ACCESS_KEY_SECRET`,
`STORAGE_S3_REGION`: new optional `s3AccessKeyId`, `s3SecretAccessKey`,
`s3Region` config, defaulting to the package's local S3 defaults.
Access-key S3 auth was previously unavailable.
- `TUS_URL_PATH=/storage/v1/upload/resumable`: the TUS `Location` header
dropped `/storage/v1`.
- `NODE_ENV=development`: the Storage image sets `NODE_ENV=production`,
and storage-api then forces `https` into TUS upload URLs. Only the
Docker runtime was affected, because native processes don't inherit the
host's `NODE_ENV`.
- Legacy parity: `UPLOAD_FILE_SIZE_LIMIT_STANDARD` (5 GB) and
`SIGNED_UPLOAD_URL_EXPIRATION_TIME` (7200s, instead of storage-api's 60s
default). Image transformation now uses the preferred
`IMAGE_TRANSFORMATION_ENABLED` key.
- The `/storage/v1` prefix is now one exported constant, also used by
the gateway route in `host/Endpoints.ts`, so the route and the Storage
config can't drift.

**CLI**

- `stack-config.ts` passes the local S3 keys and region into the Storage
creation.
- Stack `status` shows the Storage S3 URL, access keys, and region when
the Storage member enables the S3 protocol. `--env` emits
`STORAGE_S3_URL`, `S3_PROTOCOL_ACCESS_KEY_ID`,
`S3_PROTOCOL_ACCESS_KEY_SECRET`, and `S3_PROTOCOL_REGION`, matching
legacy `status` names. The previously reserved but never-emitted
`S3_PROTOCOL_URL` name is replaced by `STORAGE_S3_URL`. The same
variables appear in the `env` map of `stack start -o json`. Status omits
the S3 details for a Storage member saved without S3 keys, such as one
started before this change.

**Notes for reviewers**

- `UPLOAD_FILE_SIZE_LIMIT` is intentionally not copied from legacy.
Legacy sets it to 50 GB, which silently overrides the configured
`storage.file_size_limit`. The stack keeps enforcing the configured
limit through `FILE_SIZE_LIMIT`.
- Existing stacks will report config drift for the new Storage keys and
need a stop/start to pick them up.
- The new `StorageGateway.integration.test.ts` runs Database and Storage
behind the real gateway on native and Docker. It exercises Bun's S3
client over `/storage/v1/s3`, the TUS `Location` header, and a PATCH to
it. On macOS Docker Desktop, its TUS step fails because of #6900 (bind
mounts without xattr support), like the existing Docker storage upload
test. Linux is unaffected.

## Linked issue

Closes #6916

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

---------

Co-authored-by: avallete <andrew@snaplet.dev>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…single version table (#6883)

## Summary

slim-services now publishes immutable `<upstream>-r<N>` releases
(supabase/slim-services#326, #328). This PR makes the CLI consume them
safely and from one place:

- **Pinning.** Every slim artifact is pinned by content: the image
digest, plus an archive and manifest sha256 per target. Nothing is
looked up at runtime.
- **One version table.** The stack catalog
(`packages/stack/src/Artifacts.ts`) is the only place slim-capable
service versions are written down. The new stack, legacy `supabase
start`, and legacy slim mode (`SUPABASE_USE_SLIM_IMAGES`) all derive
from it.
- **Updates.** Upgrades and packaging hotfixes arrive as automated PRs
from slim-services releases.

### Pinning

- **Catalog entries.** They are now `ArtifactPin`s:
  - `upstreamVersion`
  - `revision`
  - `image: …:<U>-r<N>@sha256:…`
- `upstreamImage`: the upstream image slim-services built from or
mirrored, as recorded in the release
  - `natives`: sha256s per target
- **Native downloads.**
- The runtime `SHA256SUMS` and GHCR checksum lookups are gone. The
GitHub release and S3 serve bytes only.
  - The manifest is hash-checked before it is parsed.
  - A mirror that serves the wrong bytes falls through to the next one.
- The cache key is `slim-services/<svc>/<R>/<target>`, so a hotfix
revision gets its own cache entry.

### One version table

- **Generated Dockerfile lines.** The slim-capable `FROM` lines of
`apps/cli/src/shared/services/Dockerfile`, and its Go copy, are
generated from the catalog by
`apps/cli/scripts/render-service-dockerfile.ts`.
- The generator rewrites only those 14 lines, in place, and takes each
repository from the existing line. imgproxy stays on
`darthsim/imgproxy`.
- Kong, `pg14`, and the job images (migra, pg_prove,
pgadmin-schema-diff) are still maintained by hand or by Dependabot.
- A drift test in the required `apps/cli` unit project fails when the
Dockerfile and the catalog disagree.
- **Postgres 13/15 and 14.** New `pg15` (generated) and `pg14`
(hand-pinned) stages replace the hardcoded constants, in both the TS and
the Go CLI.
  - Non-slim PG13/15 moves from 15.8.1.085 to the pinned 15 line.
- Slim mode translates `pg15` through the catalog, like every other
alias.
- **Slim mode.** It now always finds its pin for the default versions. A
linked project's hosted version that the catalog doesn't pin still uses
the upstream image.

### Updates

- **`slim-release-published.yml`** reconciles the dispatched service
against every committed slim-services release. For each release line it
opens, or rewrites in place:
- a **hotfix** PR (`slim-hotfix/<svc>[-<line>]`), when the pinned
upstream has a newer revision;
- an **upgrade** PR (`slim-bump/<svc>[-<line>]`), when a newer upstream
exists on that line.
- **Each PR:**
  - pins exactly the planned release;
  - regenerates both Dockerfiles;
  - is assigned to `jgoux`.
- **Mirror timing.** Before planning, the run waits (bounded) for the
dispatched release to appear in the release list. Before pinning, it
waits (bounded) for the S3 mirror of a new release, and a digest
mismatch fails immediately.
- **Trust.** Plan and apply run from one checkout of the default branch,
so a re-run recomputes the plan and never applies a stale one. Every
`::error ::`/`::warning ::` line uses workflow-command data encoding.
The app token reaches only `git push` and `gh`: it is never written to
`.git/config`, and every `bun`/`pnpm` command (the formatter runs from
the locked install) runs without it. The PR lookup ignores fork PRs.
Payload and release-tag values are validated against anchored patterns,
and recorded image references are validated and escaped before they are
written into the catalog.
- **Dependabot.** It now ignores every slim-capable image, and the
Dependabot-to-catalog sync (`sync-artifacts-catalog.yml`) is deleted.
- **Tests.** They derive versions from the catalog, so a bump PR touches
only `Artifacts.ts` and the two Dockerfiles.

### Other changes needed by the new versions

- **Vector 0.58** no longer expands `${VAR}` in its config.
  - The recipe writes real values into its own config instead.
- In caller-supplied pipelines it fills in only `LOGFLARE_URL` and
`LOGFLARE_PRIVATE_ACCESS_TOKEN`.
  - It never enables `--dangerously-allow-env-var-interpolation`.
- **Vector on legacy `supabase start`.** Vector 0.58's images don't ship
`/etc/vector`, so the entrypoint creates it before writing the config. A
`start.lifecycle` e2e scenario starts Vector against a real Logflare.
- **`pgdelta.seam.layer.ts`** compares slim images by revision or
digest, so a container on `-r0` is reported stale once the catalog moves
to `-r1`.
- **Three integration tests that failed under load now have coherent
time budgets:**
- `upload-release-assets`: the retry that must succeed had to fit in the
same 500ms as the attempt that hangs on purpose.
- `stack-shadow`: a real-Docker test ran under vitest's 5s default
timeout.
  - `sweep-live-projects`: a 10s hard kill inside the test's budget.

### Docs

ADR 0026 is rewritten for this model, including the tradeoffs:
- slim-capable upstream and security fixes arrive only via slim-services
releases;
- Dependabot's cooldown no longer applies to them;
- kong and `pg14` are bumped by hand;
- the Deno 1 edge-runtime override stays pinned separately.

`infra/cli-artifacts/README.md` is updated to match.

## Release notes

- Unlinked local projects on Postgres 13 or 15 now start
`supabase/postgres:15.19.0.002` instead of `15.8.1.085`. Later catalog
upgrades on the 15 line move them within the same major.
- `supabase start` runs the catalog's upstream versions (table below),
and Storage is pinned to the `v1.79.28-r1` hotfix.

## Versions

Every catalog entry pins its newest committed revision. Legacy `supabase
start` now runs the same upstream versions:

| Service | Catalog before → after | Legacy Dockerfile before → after |
|---|---|---|
| postgres 17 | `17.6.1.173` → `17.11.0.002-r0` | `17.6.1.171` →
`17.11.0.002` |
| postgres 15 (PG13/15) | `15.14.1.173` → `15.19.0.002-r0` |
`15.8.1.085` (constant) → `15.19.0.002` |
| postgrest | `v16.2` → `v16.4-r0` | `v16.3` → `v16.4` |
| auth | `v2.196.0` → `v2.197.0-r0` | `v2.197.0` (unchanged) |
| realtime | `v2.134.5` → `v2.140.3-r0` | `v2.135.3` → `v2.140.3` |
| storage | `v1.73.0` → `v1.79.28-r1` | `v1.77.0` → `v1.79.28` |
| imgproxy | `v3.8.0` → `v3.26.0-r0` | `v3.8.0` → `v3.26.0` |
| edge-runtime | `v1.77.1` → `v1.77.1-r0` | `v1.77.1` (unchanged) |
| studio | `2026.09.04-sha-5a67366` → `2026.09.28-sha-5e59b60-r0` |
`2026.09.14-sha-4dd8a95` → `2026.09.28-sha-5e59b60` |
| pgmeta | `v0.99.0` → `v0.99.0-r0` | `v0.99.0` (unchanged) |
| mailpit | `v1.30.2` → `v1.31.3-r0` | `v1.30.2` → `v1.31.3` |
| analytics | `v1.50.9` → `v1.50.15-r0` | `1.50.12` → `1.50.15` |
| vector | `0.53.0` → `0.58.0-r0` | `0.53.0-alpine` → `0.58.0-alpine` |
| pooler | `v2.9.12` → `v2.9.13-r0` | `2.9.13` (unchanged) |
…#6925)

## TL;DR

The local stack's API gateway no longer opens a new upstream connection
for every request. Safe, bodyless requests (GET, HEAD, OPTIONS, TRACE)
reuse pooled keep-alive connections, so sustained read traffic stops
producing waves of `502 Bad Gateway`. Writes keep one connection per
request so they are never sent twice.

## Before

```mermaid
flowchart LR
  C[Client request] --> G[Gateway]
  G --> N["New upstream connection<br/>(every request)"]
  N --> U[Upstream]
  U --> X[Connection closed]
  X --> T["TIME_WAIT piles up"]
  T --> F["502 waves:<br/>EADDRNOTAVAIL native,<br/>forwarder resets on Docker"]
```

## After

```mermaid
flowchart LR
  C[Client request] --> R{"Safe method<br/>and no body?"}
  R -- yes --> P["Pooled keep-alive<br/>connection"]
  R -- no --> F["Fresh connection,<br/>never replayed"]
  P --> E{"Failed before<br/>a response?"}
  E -- yes --> O["One retry on a<br/>fresh connection"]
  E -- no --> D[Response]
```

## Why

Since #6897 the gateway forwarded with `agent: false`. Under sustained
concurrent `GET /rest/v1/` traffic, closed connections pile up in
`TIME_WAIT`: the native runtime fails with `connect EADDRNOTAVAIL` once
the ephemeral range is full, and on Docker Desktop the port forwarder
starts resetting connections (`socket hang up`, `ECONNRESET`). The
gateway then answers 502 until `TIME_WAIT` drains. Details and
measurements are in #6922.

#6897 moved away from pooling because Studio's MCP route closes its
connection shortly after each POST, so a pooled POST could land on a
closing connection and could not be replayed. Those POSTs now stay on
fresh connections.

## What changed

- One keep-alive agent per gateway, released with it; sockets per
upstream stay unbounded so long-lived streamed responses never queue
other requests, and idle sockets close after 4 s, before Node upstreams'
keep-alive timeout.
- Only safe, bodyless requests use the pool. Every other request keeps a
fresh connection and is never replayed, since upstreams do not
deduplicate writes.
- A single retry, always on a fresh connection, for a safe, bodyless
request whose upstream fails before responding.
- Known limitation: write-heavy traffic (PostgREST writes and RPC,
Storage uploads, function invocations) still opens one connection per
request.

Closes #6922

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

---------

Co-authored-by: avallete <andrew@snaplet.dev>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This PR was automatically created to sync the generated `@supabase/api`
package with the latest Management API OpenAPI document.

Changes were detected in the upstream OpenAPI documents exposed by
`https://api.supabase.com/api/v1-json` and
`https://api.supabase.com/api/v2-json`.

Co-authored-by: jgoux <1443499+jgoux@users.noreply.github.com>
Comment thread packages/stack/src/HttpProxy.integration.test.ts Dismissed
@supabase-cli-releaser
supabase-cli-releaser Bot merged commit 3cb948c into main Sep 30, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Approve to apply; do not merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants