chore: production deploy - #6874
Merged
Merged
Conversation
…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
…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>
…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>
## 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
…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>
jgoux
approved these changes
Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
commandswith effect lint (CLI-2408) (refactor(cli): cover allcommandswith effect lint (CLI-2408) #6849)