Repository navigation
chore: production deploy - #7104
supabase-cli-releaser[bot] wants to merge 80 commits into
Conversation
…7017) ## TL;DR reaps the containers a stack leaves behind once its directory is deleted ## whats introduced? the orphan sweep now removes containers labelled with its state root whose stack is no longer registered `stack destroy --stack-id <full id>` removes them on demand, also for a deleted `SUPABASE_HOME` when run with that home the shared database volume is left as is ## ref - closes: #6963
## TL;DR fixes the live e2e ssl reboot flake and stops develop pushes from cancelling a running live suite - `ssl-enforcement update` reboots the database and `get` can report it applied before the reboot ends, so the reboot landed in the next file and killed `migration up` with `57P01`. the test now waits for a new postmaster after the toggle and the restore, and `queryLiveDb` no longer crashes when a restart drops an idle client - merge queue bursts land develop pushes closer together than one live run, so each push cancelled the running suite and turned its commit red. pushes now share a lane that never cancels a running suite ## ref - spotted in: [migration up](https://github.com/supabase/cli/actions/runs/37284888772/job/111681321268) [cancelled run](https://github.com/supabase/cli/actions/runs/37296027587)
This bumps realtime from v2.140.10-r0 to v2.140.11-r0. https://github.com/supabase/slim-services/releases/tag/realtime-v2.140.11-r0 Planned after supabase/slim-services published v2.140.11-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This bumps storage from v1.79.36-r0 to v1.79.37-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.79.37-r0 Planned after supabase/slim-services published v1.79.37-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This bumps realtime from v2.140.11-r0 to v2.141.0-r0. https://github.com/supabase/slim-services/releases/tag/realtime-v2.141.0-r0 Planned after supabase/slim-services published v2.141.0-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This bumps realtime from v2.141.0-r0 to v2.142.0-r0. https://github.com/supabase/slim-services/releases/tag/realtime-v2.142.0-r0 Planned after supabase/slim-services published v2.142.0-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…) (#7028) ## TL;DR fixes local `functions serve --env-file` and `--no-verify-jwt` failing after a normal stack `supabase start` ## what's hurting? studio gets its Functions URL through a binding that also makes it a lifecycle dependent of Functions that means Studio blocks the restart needed to apply a temporary override, even when Studio is idle. the same guard blocks restoring the saved configuration on exit.. ## now fixed by: keeping Studio’s Functions URL binding without making Functions a startup prerequisite for studio functions can use the existing restart and cleanup paths to apply the override and restore its saved configuration studio stays in its current state ## ref: - closes: #7009
This bumps storage from v1.79.37-r0 to v1.79.38-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.79.38-r0 Planned after supabase/slim-services published v1.79.38-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…) (#7027) ## TL;DR fixes local `functions serve` failing before startup in coding-agent environments when no output format is passed ## prob/sol agent detection defaults the CLI to `json`, but stack `functions serve` only accepts `text` or `stream-json`. so the command fails with `Functions serve requires text or stream-json output` unless the caller passes an output flag now fixed by: defaulting stack `functions serve` to `stream-json` for agents when no output format is chosen explicit output choices still win... ## ref: - closes: #7010
…#7029) ## TL;DR fixes migrations with database webhook triggers failing on the stack backend with `schema "supabase_functions" does not exist` ## whats broken? the regular docker `supabase start` creates `supabase_functions` in its postgres entrypoint, which neither stack runtime runs, so webhook triggers failed on both `--runtime native` and `--runtime docker` ## now fixed by: the stack catalog creates the schema, its tables, `http_request()` and `supabase_functions_admin` before migrations, only when missing and under an advisory lock, and leaves the image's own `pg_net` policy alone existing stack databases get it on their next stop and start, and the shadow cache key moves so `db diff` rebuilds its baseline. > [!NOTE] > a migration that recreates `supabase_functions`, like the workaround in the issue now fails on the stack as it does on the regular docker start, so delete that migration file ## ref: - closes: #7007
## TL;DR fixes https requests from `http` and `pg_net` failing certificate verification on the native runtime on macOS arm64. ## whats broken? the OpenSSL bundled with native postgres looks for its CA bundle under `/nix`, which a macOS host does not have, so `http_get` to an https url failed with `unable to get local issuer certificate`. the native launcher also kept a host `SSL_CERT_FILE` away from postgres, so exporting one did not help. ## now fixed by: the native postgres launch now forwards an exported `SSL_CERT_FILE` and `SSL_CERT_DIR`, and when `SSL_CERT_FILE` is not exported it uses the first CA bundle on the host, `/etc/ssl/cert.pem` on macOS. ## ref: - closes: #7008
## Summary
The `fast-forward` job in `release.yml` fast-forwards `main` to
`develop` whenever an approving review is submitted on the develop →
main deploy PR, but it never checks who approved. On a public repo any
GitHub user can leave an approving review, so a drive-by approval on the
weekly "chore: production deploy" PR would fast-forward `main` and cut a
stable release.
The job now looks up the reviewer's repo permission first (`GET
/repos/{owner}/{repo}/collaborators/{username}/permission`, checking
both `permission` and `role_name`) and only mints the App token, checks
out and fast-forwards when it's `admin`, `maintain` or `write`. Anyone
else gets a `::notice::` and a green skip rather than a red run.
Maintainer approvals behave exactly as before.
While there, the head SHA moved out of the inline `run:` script into
`env:`.
Split out of CLI-2630 so it doesn't wait on the larger branching/release
PR, which also generalises this job.
## Linked issue
Linear: CLI-2630
- [x] The linked issue is **open** and carries the
`open-for-contribution` label (or I'm a Supabase maintainer).
## Checklist
- [x] The PR title follows [Conventional
Commits](https://www.conventionalcommits.org/) (e.g. `fix(cli): …`).
- [ ] Tests added or updated for the change. (Workflow-only change;
validated with `actionlint`. The permission lookup gets its first live
run on the next deploy PR approval, and fails closed if the lookup
errors.)
- [x] From the repository root, `pnpm check:all` passes; relevant
package tests pass for every touched workspace, and `pnpm types:check`
passes for each touched TypeScript workspace (or workspace declaring
it).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This bumps storage from v1.79.38-r0 to v1.80.0-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.80.0-r0 Planned after supabase/slim-services published v1.80.0-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The AI Review "Post review" job fails on large PRs because it fetches the PR diff from the pulls API, which returns 406 once a diff exceeds 20,000 lines or 300 files. See the failure on #7036: https://github.com/supabase/cli/actions/runs/37609410161/job/112759286813 The review jobs already build the diff locally with `generate-pr-diff.ts`, which has no size cap. The `adjudicate` job now uploads that `pr.diff` as an artifact, and `post-review` anchors inline comments against it instead of calling the API. Inline anchors now match the exact diff the reviewers saw; if the PR head moves during the run, the existing 422 fallback still folds unanchorable comments into the review body.
## Summary **TL;DR:** Two `LogForwarder` integration tests occasionally hit the 30 s timeout in CI because each assumed an ordering the forwarder doesn't guarantee. Both tests now cover every ordering without sleeps. The forwarder itself is unchanged. **Why:** CI runs on #6942 timed out in: - `pauses without advancing when Analytics refuses its credentials` - `ships a record persisted while Analytics slept once it wakes on a new port, …` **What changed** - **Credentials refusal:** the test armed its cursor-write gate only after `logflare.next` returned. When the forwarder handled the 401 and removed its cursor first, the gate never fired, and the paused forwarder never slept `pollMillis`, so both race branches hung. The test now holds the 401 answer with `logflare.hold` until the gate is armed. Delaying the test fiber after `logflare.next` reproduces the hang reliably on the old test. - **Retarget to a new port:** when the held post is released, the retired launch-1 session can reach the asleep record before the race interrupts it. It then saves that record as pending to launch 1 and fails `requireCurrent` without posting. The new session waits until the saved post deadline plus the flush window before posting, but the test expected the post without advancing the manual clock. The test now takes that post with `nextPost`, which steps through stored-id polls until a post arrives, so both orderings pass. Delaying the retarget side of the race reproduces the hang reliably on the old test. In production this ordering only delays shipping until that deadline plus the flush window (about 10 s), so the forwarder is left as is. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…7039) ## TL;DR fixes every pg_cron job run failing with `connection failed` on the native runtime ## whats broken? native postgres listens only on its socket, but pg_cron by default opens a libpq connection to `localhost:5432` for each job. nothing listens there, so every run failed with `connection failed`, or `job startup timeout` on macOS. ## now fixed by: native postgres now runs pg_cron jobs in background workers and defaults `max_worker_processes` to 17 below your own config, `ALTER SYSTEM` and `[db.settings]`. supautils stays preloaded so jobs keep their docker role policies shadow databases keep pg_cron's default mode ## ref: - closes: #6977
## Summary **TL;DR:** Database helpers now stay attached to the owning process. If that process exits or is killed, the Docker client's stdin closes, the helper exits, and Docker removes the container. A normal `docker stop` also ends the helper. ### Before Both shared-volume and per-instance helpers used detached `docker run -d` containers with a shell that ignored TERM. They could remain after the owner disappeared, and `docker stop` could not shut them down. ### After Helpers start with `docker run --rm -i` and wait for a readiness signal before commands use them. Their shell exits on stdin EOF or TERM. Existing named helpers are replaced instead of being adopted without an owner connection. The shared database volume is left intact when a helper exits. ### What changed - Give each helper a lifetime scope so its attached Docker client remains open for as long as the helper is in use, including cleanup during owner shutdown. - Preserve startup cleanup when helper creation is interrupted, and report Docker's startup error when available. - Cover attached startup, helper scope behavior, and owner SIGKILL cleanup in the storage and registry tests. ## Linked issue Closes #6962 --------- Co-authored-by: avallete <andrew.valleteau@supabase.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Andrew Valleteau <avallete@users.noreply.github.com>
…ns (CLI-2630) (#7038) ## Summary Sets up the branching and release machinery from [CLI-2630](https://linear.app/supabase/issue/CLI-2630/gh-branch-and-release-process) so we can start merging breaking-change work for v3. The design (and the review rounds behind it) lives in the Linear issue; the canonical runbook is now in `apps/cli/docs/release-process.md`, with ADR 0028 superseding ADR 0011's dist-tag section. ### What changes **New `next` channel** - `next` is a long-lived branch for breaking work. Breaking PRs target it with a `type(scope)!:` title. - semantic-release treats it as a `next` prerelease branch, so it publishes `X.Y.Z-next.N`. It goes out to npm `next` and as a GitHub prerelease, with no brew/scoop. - In `release.yml`, a push to `next` runs `test.yml` first, and only plans and releases once that passes. - `-next.N` builds identify as "Supabase CLI (next channel)." **Syncing (`sync-branches.yml`, replaces `sync-main-to-develop.yml`)** - Two pairs only: main → develop after a stable release, and develop → next on every develop push. - A clean merge is pushed directly. - On a conflict it opens a `sync/<source>-into-<target>` PR at the source tip and pauses that pair until the PR closes. Someone resolves it by merging the target in. **Approve-to-fast-forward (`fast-forward.yml`, moved out of `release.yml`)** - Covers the deploy PR (develop → main), the major cut (next → develop), and the two bot-authored sync PRs. - Before pushing it checks: - the approver has write access - the approved commit is still the PR head - the 4 required checks are green, read from this PR's own check rollup so another PR's runs on the same commit don't count - `--ff-only` (sync PRs re-sync and ask for re-approval if the target moved) - Anything that lands on `main` also refuses a major bump without the `release-major` label. - `test.yml` now runs on PRs into `main`, so the deploy PR head carries the required checks. Hotfix PRs into `main` finally get CI too. **Maintenance lines (`v<N>.x`)** - Released by running `release.yml` on the branch itself (`gh workflow run release.yml --ref v2.x -f channel=maintenance`). Its own frozen workflow, scripts and live e2e tests are used. - A wildcard semantic-release entry (`v+([0-9]).x`) maps `v2.x` to range `2.x`, channel `v2.stable`. - Publishes to npm `v<N>.stable`, Homebrew `supabase@<N>` (with `conflicts_with "supabase"`) and Scoop `supabase-v<N>-stable`. - Releases are published with `--latest=false`. - The live e2e gate takes a branch input. **Guards** - Lint Pull Request hard-fails a breaking `!` title unless the base is `next`. It runs the guard from the default branch, and the cut exemption only applies to same-repo branches. - `branch-policy.yml` adds two checks: - `Require fast-forward`, which keeps the merge button off sync and cut PRs - `Check maintenance source`, which only allows same-repo `hotfix/*` or `backport/*` into `v*.x` - `bot-pr-auto-merge.yml` skips the two sync PRs. - Manual `release.yml` runs: - refuse `stable`/`beta` from `next` or `v*.x` - only run a non-dry-run `stable` from `main` or `hotfix/*` - validate the version string **Also** - The AI review adds a non-blocking "Next impact (advisory)" section on PRs into develop that conflict with work on `next`. - Release scripts accept the `next` and `v<N>.stable` tags. - Backfill/apply release notes understand `-next` and maintenance tags. - Nightly live e2e also covers `main` and `next`. - Root `AGENTS.md`, `MAINTAINERS.md` and `CONTRIBUTING.md` cover branch targeting and the release-infra cherry-pick rule. ### Deliberately not here - No `next` dist-tag move on stable releases, because publishing stays OIDC-only (`npm dist-tag` would need a long-lived token). - Brew/scoop `supabase-next`, a Linear `next` pipeline and an `@supabase/config` next channel are deferred until someone asks. - One writer may resolve *and* approve a sync conflict PR. That's an accepted decision, documented in the runbook. ### After merge (manual, in this order) 1. Create `next` from the `develop` tip. 2. Ruleset "Default (next)": copy of develop's (squash merge queue, the 4 required checks, code-owner review), with the release App as bypass actor. 3. Add `Require fast-forward` as a required check on `develop` and `next`. 4. Ruleset for `refs/heads/v*.x` with `Check maintenance source` required. 5. Create the `release-major` label. 6. Confirm the App is a bypass actor on `develop`, and that npm trusted publishing isn't restricted by branch. Details are in `.github/MAINTAINERS.md` → "Branches and releases". ### Heads-up - #7035 (merged) added an approver check to the old fast-forward job in `release.yml`. This PR removes that job, and `fast-forward.yml` carries the same check (`.permission` exactly `admin | write`); the merge conflict is already resolved on this branch. - `lint-pull-request.yml` is `pull_request_target`, so the new title guard only starts running once this is on `develop`. - The fast-forward and sync paths can't be fully exercised before merge. The first deploy PR approval and first develop push after `next` exists are the real tests, so it's worth watching those runs. ## Linked issue Linear: [CLI-2630](https://linear.app/supabase/issue/CLI-2630/gh-branch-and-release-process) - [x] The linked issue is **open** and carries the `open-for-contribution` label (or I'm a Supabase maintainer). ## Checklist - [x] The PR title follows [Conventional Commits](https://www.conventionalcommits.org/) (e.g. `fix(cli): …`). - [x] Tests added or updated for the change. (`.github/scripts`: 372 bun tests incl. real-git temp repos for sync/fast-forward; `apps/cli` unit tests for release channels, title analysis and the build channel.) - [x] From the repository root, `pnpm check:all` passes; relevant package tests pass for every touched workspace, and `pnpm types:check` passes for each touched TypeScript workspace (or workspace declaring it). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## TL;DR Studio's Logs → API Gateway page was always empty on the experimental stack, because the stack's API proxy logged nothing per request. The proxy now writes one access line per request, persists it like service logs, and ships it to Analytics in the shape Studio reads, so the page lists every request with its status, method and path. `supabase stack logs` shows the same lines. Stacked on #6893. ## Before ```mermaid flowchart LR C[Client] --> P["Stack API proxy"] P --> S["REST / Auth / Storage / Functions / Realtime"] S --> L["Service logs"] L --> A[Analytics] P -. "no access log" .-> X["Studio API Gateway: empty"] ``` ## After ```mermaid flowchart LR C[Client] --> P["Stack API proxy"] P --> S["REST / Auth / Storage / Functions / Realtime"] P --> G["gateway log stream<br/>one line per request"] G --> F["logs/gateway files"] F --> CLI["supabase stack logs --service gateway"] F --> A["Analytics cloudflare.logs.prod"] A --> U["Studio API Gateway"] ``` ## Why The legacy `supabase start` fills the API Gateway page from Kong's access log (source `cloudflare.logs.prod`). The experimental stack has no Kong: its shared API port is served by the owner's own HTTP proxy, which had no access log, so the page showed "No data" however much traffic the stack served. ## What changed - The shared API proxy records one access line per request and per websocket upgrade, in nginx combined format plus the duration. The status is the one actually sent, including proxy-generated 404/502/503 and requests that wait for a lazy service to wake; a client that leaves before the response is recorded as 499, and body bytes appear only for responses that finished. Logging never delays a response. - `apikey`, `access_token` and `token` query values are replaced by `redacted` before the line is written; request headers such as `Authorization` are not logged. - The lines are kept as a `gateway` log stream under the stack's `logs/gateway/` directory with the same rotation and retention as service logs, survive owner restarts, and are removed with the stack. - While Analytics is running and healthy, gateway lines ship to `cloudflare.logs.prod` with the Kong request/response metadata Studio's API Gateway list, detail panel and status filter read (the path without its query, the query as `search`). - `supabase stack logs` includes gateway lines by default when the stack serves the shared API port, and `--service gateway` selects them; reading works while the stack is stopped. - Studio's per-function logs page stays empty: it filters on a function id that local Studio regenerates on every call, and the local edge runtime output does not carry the function name. The legacy start has the same gap. ## Terminal captures Recorded from this branch's source on macOS (Docker runtime): terminal with vhs, Studio in headless Chrome, at the same time against the same stack. Top: `stack logs -f --service gateway` (the `apikey` query value is redacted); bottom: requests to REST, Auth, Storage and Functions; right: Studio's API Gateway list filling in, then the error and warning status filter. Before this change the page showed "No data".  🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ls (#7041) ## TL;DR retries the network restrictions live update once when the platform stalls it, and makes the analytics poll say what stalled - `network-restrictions update` failed with `expected 1 to be +0` and no stderr. the platform's `apply` stalled until the harness killed the cli at the 60s exit guard. the update is now re-issued once when it was killed before printing its result, the same way the restore already retries, and a second stall fails naming the kill - the Logflare stored id poll in `Owner.analytics` ran out with a bare `NoSuchElementError` after Analytics left healthy mid test. it now fails with `NotStoredError` naming the first or sentinel event, its id and the poll count ## ref - spotted in: [network restrictions](https://github.com/supabase/cli/actions/runs/37436825182/job/112180576049) [analytics](https://github.com/supabase/cli/actions/runs/37451747630/job/112229699817)
#7036) ## Why `@supabase/stack` runs a local Supabase anywhere: in containers (Docker or Podman) or as native processes on macOS and Linux, with several stacks running side by side. This PR is an architecture and hardening pass over that runtime and the experimental `supabase stack` commands (still behind `SUPABASE_EXPERIMENTAL_STACK=1`). The aim is a design that is robust because it is simple: - one owner per fact; - derive state instead of recording it; - prevent conflicts by construction; - rely on guarantees the OS and the engine already give: kernel locks, atomic rename, pipe EOF and container labels. Several mechanisms were deleted rather than patched. The legacy `supabase start` path is untouched. ## What users stop hitting | Situation | Before | After | |---|---|---| | `supabase stack stop` | Listeners closed first, so Vector couldn't flush logs through the proxy and was force-killed after its grace period. Every stop paid that delay, plus an analytics drain and a 10 s hold for idle pooled connections. | Services stop in reverse dependency order with their listeners still open. Each listener closes when its own service has stopped. | | A service crashes | The proxy could keep routing to the dead process while cleanup ran, and a retried cleanup could lose the crash outcome. | The crash is reported first, so routing stops at once. The service ends `Failed` even across retried cleanup. It wakes again on demand, behind a crash-loop breaker. | | Restarting a stack | Public ports could change between `stop` and `start`, breaking URLs embedded in apps. | Automatic ports are reserved per OS user and kept across stop and restart and across state roots until destroy. Configured ports are held only while the stack runs, so a stopped project never blocks another. A conflict names the port, the holding project and the setting to change. | | Two native stacks on macOS | Wildcard binds let another stack's same-port loopback bind shadow a service, so requests hit the wrong stack ("Invalid JWT"). | Native services bind loopback only, and backend ports come from a dedicated range below the OS ephemeral range. | | Proxy retry after a backend restart | The retry reused the address captured before the first attempt and could reach a foreign process that took the port. | The address lives in the lifecycle's `Running` state and is re-resolved on retry. A dead workload has no routable address. | | Deleting a stack's state directory | The owner kept running and its containers leaked. | The owner notices that it lost ownership, stops only its own services without touching data or ports, and exits. Leftover labelled containers are swept by the next container stack under that state root, and `destroy --stack-id` reaches them on demand. | | `destroy` | Helper containers and files under the data root could survive, and open leases made destroy fail. | Everything labelled with the stack is removed, owned data is removed by location, and destroy succeeds despite open leases. | | Engine not running | Startup and destroy failed in opaque ways. | Startup and `destroy` fail with `runtime-unavailable`. `destroy` leaves data and ports untouched and prints the exact command to retry once the engine is back. | | Podman | Not supported. | Podman is a first-class engine. The CLI picks Docker, then Podman, then native. Rootless launches keep file ownership with `--userns=keep-id`. | | Changing stack credentials | A stopped consumer kept stale keys baked into its saved config. | Credential changes refresh the stopped consumers in the same locked save, keeping values set explicitly. | | Concurrent native preparers | Two processes preparing the same binaries could lose files. macOS temp-dir aging could empty a cached generation. | Artifacts are immutable, content-addressed generations published by rename into the user cache directory. | | Config change on a saved stack | Rejected with an opaque error. | The error names the changed settings and the exact recreate command. | ## How it works now ### One lifecycle owner per service Start, stop, sleep, restart, crash handling and destroy used to have separate writers. A single pure reducer now decides every transition under one gate. The runtime only executes the step it is given, and late outcomes are fenced by generation. ```mermaid stateDiagram-v2 [*] --> Stopped Stopped --> Starting: demand or explicit start Starting --> Running: session available Starting --> Failed: launch failed, cleanup confirmed Running --> Stopping: stop, restart, idle sleep or crash Stopping --> Stopped: stop / restart / sleep, cleanup confirmed Stopping --> Failed: crash, cleanup confirmed Stopping --> Stopping: cleanup failed, retried on request Failed --> Starting: demand, behind the breaker Failed --> Stopped: explicit stop ``` - **Routing.** An address exists only in `Running`, so traffic can't reach a stopping or crashed workload. - **Breaker.** It opens for 30 s after 3 failures, each within 5 minutes of the previous one. - **Wake waiters.** Requests that wake a sleeping service wait up to 120 s. - **Dependencies.** Prerequisites stay awake while a dependent runs. ### Ordered stop instead of a listener drain ```mermaid sequenceDiagram participant P as Stack proxy participant F as Functions participant A as Auth participant V as Vector participant DB as Postgres Note over P: Draining (one-way): wake-up traffic refused,<br/>traffic to running services still admitted P->>F: stop (dependents first) F-->>P: stopped, listener closes P->>A: stop A-->>P: stopped, listener closes V->>P: final log flush still reaches analytics P->>V: stop V-->>P: stopped, listener closes P->>DB: stop (prerequisites last) DB-->>P: stopped, listener closes ``` Draining is one-way. If stop or destroy fails, the stack stays registered and the next command reclaims it under the lease. There are no resume or recovery paths. ### Who owns what ```mermaid flowchart TB subgraph user["Per OS user"] ports[("~/.supabase/ports.sqlite<br/>public port reservations")] cache["~/.cache artifact generations<br/>immutable, content-addressed"] end subgraph root["State root (listing is per root)"] lock["<id>/owner.lock<br/>SQLite lease = stack is live"] state["<id>/state.json<br/>registration and definitions"] data["<id>/data/<instance><br/>owned by location"] end engine["Engine containers<br/>com.supabase.stack + com.supabase.stack-root labels"] owner(("Stack owner process")) owner -- holds --> lock owner -- polls --> state owner -- sweeps by label --> engine owner -- reserves --> ports owner -- removes on destroy --> data ``` - **Liveness.** A stack is live exactly while its lease is held, and the OS releases the lease when the process exits for any reason. - **Containers.** They are tracked only by two labels set atomically at creation. The claims journal is gone. - **Data.** Ownership is by location: everything under an instance or data root is owned. Borrowed caller paths may not resolve inside it. Destroy checks containment before removing. - **Ports.** Reservations live only in the per-user registry; `state.json` stores none. Automatic ports stay reserved until destroy; configured ports are held only while their listener is open, and a crashed holder's reservation is taken over. - **Engines.** Each container stack pins one engine target (Docker context or host, Podman connection or URL), and every engine call uses it. ### Removed or intentionally unsupported | Item | What changed | |---|---| | Resource-claims journal, listener-drain deadline, abandon mode and its retry loop, in-process artifact lock registry | Replaced by labels, ordered stop, an owner that exits when it loses ownership, and SQLite file locks. | | Stacks saved by earlier experimental releases | No migration. A saved stack that can't be read fails with an error naming its directory to remove. | | Filesystems without hard links | Unsupported; the non-atomic fallback was removed. | | Native mode on Windows | Out of scope; Windows runs the stack in containers. | | Container sweeps in native mode | Native owners skip them; native mode has no container concerns. | | Port reservations across OS users | Not provided; kernel binds still keep different users' running stacks apart. | | Proxy completion window during stop | None; in-flight requests survive as far as each service's own graceful stop carries them. | ## Size 240 files changed: +23,442 / −11,645 (**+11,797 net**). About two thirds of the growth is tests. | Area | Kind | Net lines | |---|---|---:| | `packages/stack` | Production code | +3,277 | | | Integration tests | +3,960 | | | Unit tests | +1,605 | | | Test helpers | +1,288 | | | E2E tests | +897 | | | Docs | +64 | | `apps/cli` | Production code | −90 | | | Tests | +123 | | | Docs | +44 | | Repo root | Namespace import-boundary lint rules | +380 | | | CI (Podman on rootless Linux, stress workflow) | +188 | | | ADRs | +58 | | **Total** | Production code | **+3,187** | | | Tests | **+7,873** | The production growth is in the stack runtime only. The CLI's production code is slightly smaller.
This PR was automatically created to sync API types from the infrastructure repository. Changes were detected after refreshing `apps/cli-go/api/v1-openapi.yaml` from the latest spec from infrastructure, and `apps/cli-go/pkg/api` is regenerated from it. The snapshot diff is the upstream API change and is worth reading: it is marked `linguist-generated`, so GitHub collapses it behind a "Load diff" click rather than hiding it. Co-authored-by: supabase-cli-releaser[bot] <246109035+supabase-cli-releaser[bot]@users.noreply.github.com>
) ## TL;DR lets `stack destroy` take `--stack-id` more than once, destroying every listed stack after a single confirmation ## whats introduced? every id resolves before the prompt, so a missing one destroys nothing and a prefix plus the full id of one stack count once. a stack that fails to destroy doesn't stop the rest, and the command still exits 1 with one error naming the failed ids and a retry command example: ``` $ supabase stack destroy --stack-id <a> --stack-id <b> --yes Permanently destroying stack <a> at /work/app and its owned data; stack <b> at /work/app and its owned data. Storage upload files will be preserved. Stack <a> destroyed. Stack <b> destroyed. ``` > [!NOTE] > a single id behaves exactly as before. with more than one `--stack-id` json returns `stacks` and a partly failed batch emits one error envelope carrying `destroyed_stacks` ## ref - closes: #6964 --------- Co-authored-by: 7ttp <117663341+7ttp@users.noreply.github.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**: develop's `supabase` type check fails since the Management API sync in #7054 because the hand-kept project-config mirror lacks four new `postgres_settings` keys. This adds them, and fixes the turbo cache key that let the sync PR (and develop's merge-queue run) replay a cached pass instead of type-checking. **Before** ```mermaid flowchart LR A["Sync PR changes packages/api"] --> B["supabase#types:check hash unchanged"] B --> C["Cache hit: replayed pass"] C --> D["Auto-merge; develop broken"] ``` **After** ```mermaid flowchart LR A["Sync PR changes packages/api"] --> B["@supabase/api#transit hash changes"] B --> C["supabase#types:check re-runs"] C --> D["Drift guard fails the sync PR"] ``` ## Why On develop at 2286e8c, an uncached `pnpm run types:check` in `apps/cli` fails: ``` src/shared/config/project-config-api-drift.unit.test.ts(45,3): error TS2344: Type '"autovacuum_max_workers" | "autovacuum_work_mem" | "pg_net_batch_size" | "pg_stat_statements_max"' does not satisfy the constraint 'never'. ``` Develop's CI showed green only because turbo replayed a cached `supabase:types:check` ("cache hit, replaying logs") on the #7054 merge-queue run. `types:check` had no `dependsOn`, so its hash covered only the package's own files: a change confined to `packages/api` (or `packages/config`) never invalidated a dependent's cached type check. Any PR that touches `apps/cli` misses the cache and fails "Check code quality" (e.g. #6934). Sync PRs are auto-merged, so the drift guard has to fail on the sync PR itself. ## What changed - `packages/config` mirror (`ProjectConfigApiAttributes`): add `autovacuum_max_workers`, `autovacuum_work_mem`, `pg_net_batch_size` and `pg_stat_statements_max` as unmapped `Schema.Unknown` fields. They follow their API-only siblings (`log_*`, `checkpoint_timeout`, `hot_standby_feedback`, `cron_log_statement`): no `[db.settings]` key, no Go CLI counterpart, still reachable through `_apiResponse`. Making them configurable in `config.toml` would be a separate published-schema change. - `turbo.json`: add a `transit` task (`dependsOn: ["^transit"]`) and make `types:check` (including `@supabase/docs#types:check`) depend on it. Each workspace dependency's files now feed the dependent's cache key without serialising the type checks. - `packages/api/scripts/dependent-types-check-cache.unit.test.ts`: asks turbo's dry-run graph whether every workspace that depends on `@supabase/api` reaches `@supabase/api#transit` from its `types:check`. It fails on the previous `turbo.json`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This bumps storage from v1.80.0-r0 to v1.80.2-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.80.2-r0 Planned after supabase/slim-services published v1.80.1-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…nt (CLI-2653) (#7045) ## TL;DR brings the `db` connection string parser, `pgpass` and `pgservicefile` under the effect lint ## whats introduced? - `db-config.parse`, `pgpass` and `pgservicefile` join the allow list file by file, so the rest of the `db` module waits for the later PRs in this stack - the parser reads `pgpass` and the service file through the `FileSystem` and `Path` services, and its libpq env reads go through `Config` once up front - `parseConnectionString` and `poolerConfigFromConnectionString` become effects, and every caller across `db`, `stack`, `test-db`, `migration squash`, `gen types` and the pg-delta engine yields them - an empty shell var still wins over the project `.env`, a missing `pgpass` or service file still reads as absent, and every parse error keeps its text - unit tests run on temp dirs and a pinned `Config` env instead of `process.env` writes, with a new case for the empty shell var ## ref - related: CLI-2653
) (#7046) ## TL;DR brings the `db` pg connection layer and few resolvers under the effect lint ## whats introduced? - `db-connection` and `db-dns` join the allow list - the `sslrootcert`, `sslcert` and `sslkey` reads go through `FileSystem`, keeping the BOM and the errno messages, and `dbConnectionLayer` captures it at build so `DbConnection.connect` keeps its type - a cert path with a NUL byte, only reachable as `%00` in a DSN, now reads `FileSystem.readFile: ...` instead of `TypeError [ERR_INVALID_ARG_VALUE]: ...`, since the platform keeps only the message for that throw - `--dns-resolver https` sends the same request through `HttpClient` with automatic HTTP tracing disabled, so no trace headers reach Cloudflare, and keeps every failure text and the 10s timeout; a `Db.resolveHostsOverHttps` span records the response status and answer count - tests pin the full cert read errors and cover the DoH resolver: non-200, rejected request, no valid IP, invalid and empty JSON and the timeout ## ref - related: CLI-2653
…I-2653) (#7048) ## TL;DR brings the `db` config toml reader under the effect lint ## whats introduced? - `db-config.toml-read` joins the allow list - shell env reads go through `Config` once, for the override names and every `env(...)` ref in the toml, and the sync `envLookup` reads from that snapshot - `config push` drops its copy of the env ref helpers and uses the reader's - the toml parse runs before the env snapshot with the same failure order and messages - invalid signing keys files retain native JSON parser details in both `config.toml` loading and `local-config-values`, with the same narrow lint exception used by the SSO readers - unit tests pin env through `Config` and temp dirs instead of `process.env` writes, and the push, reset and start integration tests pin the three values the reader now takes from `Config` ## ref - related: CLI-2653
…I-2653) (#7049) ## TL;DR brings the rest of the `db` module under the effect lint ## whats introduced? - the per file `db-config.parse`, `db-config.toml-read`, `db-connection` and `db-dns` entries collapse into `db-*.ts`, and `branch-target`, `connect-errors`, `pooler-fallback` and `schema-flags` join, which covers the whole `db` module - `db-config.layer`, `db-pull-run` and `db-push-core` yield their errors directly with the same classes and messages, and `branch-target` recovers through `Effect.option` and `orElseSucceed` - `db-config` integration tests feed env through `Config` instead of `process.env` hooks, and `db-image` and `db-target-flags` move to `it.effect` ## ref - closes: CLI-2653
## TL;DR syncs the shared effect skill with the latest upstream, which was refreshed for stable effect v4. ## whats introduced? - adds `references/V4_APIS.md` with the current v4 imports and common migration traps - updates `SKILL.md` and the config, schema, caching, http and testing references for the current v4 apis ## ref - extends: #6688
…7093) ## TL;DR restarts a stack service's running dependents when the service crashes, so none keeps state bound to the dead process. ## whats broken? when a prerequisite crashes, the stack relaunches it but leaves its dependents running. A dependent that pools connections to it keeps those dead connections: after a database crash, Auth answers with 500s until each dead pooled connection has been used once. ## now fixed by: - a lost session stops every starting or running transitive dependent. - that stop is not a crash: it doesn't count toward the dependent's crash breaker or change its intent, and the dependent relaunches once the prerequisite's new generation is ready, if it still has demand. Dependents that weren't up stay as they are. - `ARCHITECTURE.md` and the stack README describe the new behavior. ## ref: - follows up: #7080 (comment)
Bumps `@supabase/pg-delta` from `1.0.0-alpha.58` to `1.0.0-alpha.59` and updates the release-age exclusion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This bumps storage from v1.81.0-r0 to v1.81.1-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.81.1-r0 Planned after supabase/slim-services published v1.81.1-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…7096) ## TL;DR brings the `docker` module under the effect lint ## whats introduced? - `container-cli` and the `docker-*` files join the allow list - `docker-lifecycle` reads container output through `collectText` from `container-cli` instead of its own copy - the image pull deadline reads `Clock`, and the docker errors are yielded directly with the same messages - `resolveLocalProjectId` takes the `Path` service, so `pgdelta`, `db-pull-run`, `local-project-context` and the `db diff` and declarative handlers pass it along - the `docker-registry` tests pass the shell registry through an explicit `ConfigProvider`, and `docker-image-resolve` drops its `process.env` registry lines, which the project env always overrode ## ref - related: CLI-2652
…fect lint (CLI-2652) (#7100) ## TL;DR brings the `edge-runtime` and `storage` modules under the effect lint ## whats introduced? - the `edge-runtime-*`, `storage-*` and `detect-content-type` files join the allow list - `edge-runtime-script.layer` yields its errors directly with the same messages, and its tests move to `it.effect` with scoped temp dirs instead of `node:fs` - `withStorageGatewayFetch` replaces the hand rolled CA fetch, so gateway calls pass Bun's `tls.ca` through `FetchHttpClient.RequestInit` and still skip DNS over HTTPS - `storage-credentials` reads `SUPABASE_AUTH_SERVICE_ROLE_KEY` through `Config` - the gateway decodes response bodies with a schema codec, so only the parse detail of a non JSON body changes - the `StorageGateway` list calls become plain effects and `contentTypeForUpload` takes `Path`, so `seed buckets`, `start` and the `storage` commands follow - the `storage-url` errors move to `Data.Error` with the same text ## ref - closes: CLI-2652
## TL;DR moves the first half of the commands that predate the effect skill onto it.. ## whats introduced? <details> <summary>commands covered</summary> `sso` `login` `logout` `orgs` `whoami` `encryption` `projects` `branches` `backups` `domains` `vanity-subdomains` `ssl-enforcement` `network-bans` `network-restrictions` `postgres-config` `secrets` `snippets` `functions` `storage` `config` `gen` `pull` `notebooks` `feedback` `completion` `issue` `compute` </details> - error recovery narrows to `catchTag`, `catchReason` and `catchIf` instead of `Effect.flip` and `_tag` checks - casts and non null assertions give way to `Predicate` narrowing and schema decoding - notebooks, storage and compute logs page through `Stream.paginate`, in the same request order as before - helpers use `Effect.fn`, adding spans for cname checks, compute discovery, signing key generation and notebook listing - raw `sso` requests reuse the shared `requestWithAuth` helper - tests run on `it.effect` and wait on `Deferred` instead of polling, staying on `it.live` only where a real clock, stdin or backoff is involved ## notes - no user facing behaviour is changed - output, exit codes, errors and telemetry match develop, and the new spans only appear in trace output and the `SUPABASE_DEBUG=1` span console - `stack` is left out since it was written alongside the skill imo - `start`, `stop`, `db` & the rest of the commands are planned as followup ## ref: - related: CLI-2693 - builds on: #6688
This bumps storage from v1.81.1-r0 to v1.81.3-r0. https://github.com/supabase/slim-services/releases/tag/storage-v1.81.3-r0 Planned after supabase/slim-services published v1.81.3-r0. This branch is rewritten from develop whenever a newer relevant release arrives. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Updates the default model of the AI release-notes generator (`apps/cli/scripts/propose-release-notes.ts`) from `claude-haiku-4-5-20251001` to `claude-haiku-5-5`. The propose and backfill workflows don't override `--model`, so both use the new default. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…ecation warnings (#7108) ## Summary v3 removes `db remote commit` and three low-use `inspect db` aliases (CLI-2595, on `next`). This updates their stable deprecation warnings so users see the removal coming and know what to switch to: | Command | Warning now says | | -- | -- | | `db remote commit` | `Command "commit" is deprecated and will be removed in v3, use "db pull" instead.` | | `inspect db total-index-size` | `… will be removed in v3, use "index-stats" instead.` | | `inspect db total-table-sizes` | `… will be removed in v3, use "table-stats" instead.` | | `inspect db table-index-sizes` | `… will be removed in v3, use "table-stats" instead.` | The help descriptions also mention the v3 removal. The warnings still go to stderr only, never to stdout or JSON output. The higher-use `inspect db` aliases keep their current notice because v3 doesn't remove them. ## Linked issue Linear: CLI-2598 (parent CLI-2595) ## Checklist - [x] The PR title follows Conventional Commits. - [x] Tests added or updated for the change: integration tests assert the exact v3 text on stderr, and that it never reaches stdout. - [x] From the repository root, `pnpm check:all` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…7083) ## TL;DR `next` now catches up with `develop` without a maintainer hand-resolving every conflict. When a push to `develop` conflicts with `next`, Claude resolves the existing sync PR, and once CI and the AI review finish it fixes what the merge broke and answers the review. Landing on `next` still needs a maintainer's approval. ## Before ```mermaid flowchart LR push["push to develop"] --> merge{"merge into next"} merge -->|clean| next[("next")] merge -->|conflict| pr["sync PR<br/>(develop tip)"] pr --> human["maintainer merges next,<br/>resolves, pushes"] human --> approve["approve"] --> next ``` ## After ```mermaid flowchart LR push["push to develop"] --> merge{"merge into next"} merge -->|clean| next[("next")] merge -->|conflict| pr["sync PR<br/>(develop tip)"] pr --> claude["Claude resolves,<br/>push + AI review"] claude --> settle{"CI + review done"} settle -->|"failures or findings"| repair["Claude repair round<br/>(max 2)"] --> settle settle -->|green| approve["approve"] --> next repair -->|"limit hit"| ping["ping @supabase/cli"] claude -->|"cannot resolve"| human["maintainer resolves"] ``` ## Why Every `develop` → `next` conflict waited for a maintainer to merge, resolve, and push by hand, and later syncs skipped while the PR stayed open, so `next` drifted further behind each day. ## What changed - **Resolve** (`Sync branches`): after a `develop-into-next` conflict PR opens, Claude merges `next` into the sync branch with file read, search, and edit tools only: no shell, no web tools, no `.git` edits, and no GitHub token. It is asked to update every remaining use of anything one side renamed or removed, including files that did not conflict, and keeps `next`'s `pnpm-lock.yaml` for regeneration. A second job with no secrets formats the tree, regenerates the lockfile, and runs `check:all` and unit tests; a third lets Claude fix what fails once. The jobs holding the Anthropic key never execute the merged tree, which moves between jobs as a git bundle. The publish job pushes the latest finished stage from a fresh checkout with the release App token, comments the resolution, and starts the AI review. Choices between `develop` and `next` behavior are listed as decisions for `@supabase/cli`. If Claude cannot resolve the merge, it says why and the PR waits for a person as before. - **Repair** (`Sync repair`, new): when a CI workflow or the AI review finishes, and hourly, it waits until every check on the PR head is done and the PR's own AI review run has finished. It then gives Claude the failed jobs' log tails and the AI review's unanswered threads. Claude fixes what the merge introduced and declines the rest as out of scope; the bot pushes the fix, replies on each thread, and resolves the fixed ones. At most two rounds per PR; once `@supabase/cli` is asked to take over, repair stops for that PR. - **Trust**: Claude reads only code already in `develop` or `next`, CI output, and the AI review's own threads, never other people's comments. Workflows on the sync branch run with repository secrets, so Claude never pushes a `.github/` change: it resolves and fixes workflow files like any other, but those edits are posted as review suggestions (a diff for added or deleted files), and a conflicted workflow keeps `develop`'s side until a maintainer uses **Commit suggestions**. Publish refuses a push whose `.github/` differs from git's own merge, the repair token has no `workflows` permission, and repair waits for pending suggestions. Claude's other edits run in the sync PR's CI before review, and every sync PR lands only through a maintainer's approval. - **AI review**: each run is named after its PR, and its Codex steps accept runs dispatched as `github-actions[bot]`, which the sync workflow uses to review its own PR; without it the review failed and nothing was posted. - `main-into-develop` is unchanged. The runbook and maintainer guide describe the new flow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adds `.github/ai-models.json`, which holds model ids keyed by family and tier (`claude.opus`, `claude.sonnet`, `claude.haiku`, `codex.sol`). Bumping a tier there upgrades every consumer of that tier. - `ai-review.yml` drops the hardcoded `CLAUDE_MODEL` / `CODEX_MODEL` workflow env. The `resolve` job reads both from the trusted default-branch copy and passes them to the other jobs as outputs, so a PR can't change the model that reviews it and every job in a run uses the same models. - `sync-branches.yml` and `sync-repair.yml` read `claude.opus` from the file instead of pinning it. - `propose-release-notes.ts` imports the file and defaults `--model` to `claude.haiku`. The file is listed as release infrastructure in `release-process.md`, so a model change there may need porting to active `v*.x` lines. A model bump can still need the Claude Code or Codex CLI version pinned in these workflows bumped too; the file's `$comment` says so. The review reads the file from `develop`, so the AI review on this PR fails at `resolve` until it merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
| const failures = await Promise.all( | ||
| plan.failures.map( | ||
| async (run) => | ||
| `## ${run.name} (${run.conclusion})\n\n\`\`\`\`text\n${await logTail(token, repository, run.id)}\n\`\`\`\`\n`, |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
CI log text is copied verbatim inside a fixed four-backtick fence; contributor-controlled test output can emit that fence and place instructions in the repair context. Claude can edit and commit repository files, and the repair workflow pushes its changes to the sync PR, enabling attacker-directed application-code changes.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Use the existing codeFence() helper instead of a hardcoded four-backtick fence. It sizes the fence to be longer than any backtick run in the log text, so CI output can't close the block and inject instructions. Note that this only stops the fence breakout. The log content is still untrusted, so the repair prompt should also tell Claude to treat ci-failures.md as data, not instructions.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| `## ${run.name} (${run.conclusion})\n\n\`\`\`\`text\n${await logTail(token, repository, run.id)}\n\`\`\`\`\n`, | |
| `## ${run.name} (${run.conclusion})\n\n${codeFence(await logTail(token, repository, run.id), "text").join("\n")}\n`, |
| "Files changed on `next` since it diverged from `develop`:", | ||
| "", | ||
| "```", | ||
| ...capped(statLines, MAX_STAT_LINES), |
There was a problem hiding this comment.
⚪ Severity: LOW
A next-branch filename containing triple backticks reaches statLines unchanged and closes this fence in the context delivered to Claude/Codex. The filename can append instructions that bias review of PRs into develop, potentially suppressing security findings in this AI-review control.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Neutralize backticks in the git diff --stat lines before they go inside the triple-backtick fence, the same way commit subjects are already sanitized. Pass statLines through sanitizeSubject (which replaces with ') before callingcapped. Filenames on next` can then no longer close the fence and inject instructions into the AI review context.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| ...capped(statLines, MAX_STAT_LINES), | |
| ...capped(statLines.map(sanitizeSubject), MAX_STAT_LINES), |
Sync repair filtered AI Review completions with `workflow_run.name == 'AI Review'`, but `ai-review.yml` sets `run-name: AI Review #<pr>`, so `workflow_run.name` never matched. When the AI review finished after CI, repair skipped and only the hourly cron could restart it (seen on #7112). The filter now matches on the workflow file path. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary **TL;DR** When the new local stack fails because a container engine command failed, telemetry now says which step failed (create, start, inspect, engine target lookup, remove, or wait) instead of one shared `engine_command` fingerprint. Recent prereleases surface `engine_command` as the main cause of Docker `start` failures on Linux, but not which Docker call breaks, so the failure couldn't be narrowed down further. ### Before ```mermaid flowchart LR A["docker create / start / rm / ..."] -->|fails| B["ContainerError<br/>operation"] B --> C["kind engine-command"] C --> D["…:engine_command"] ``` ### After ```mermaid flowchart LR A["docker create / start / rm / ..."] -->|fails| B["ContainerError<br/>operation"] B --> C["kind engine-create,<br/>engine-start, …"] C --> D["…:engine_command_create,<br/>…:engine_command_start, …"] ``` ### What changed - `@supabase/stack` classifies a `ContainerError` with no finer cause by its engine operation: - `engine-create` ← `create` - `engine-start` ← `start` - `engine-inspect` ← `inspect`, `image`, `ps` - `engine-target` ← `info`, `system`, `context`, identity and connection checks - `engine-remove` ← `rm`, `stop`, `kill`, cleanup - `engine-wait` ← `wait` Any other operation stays `engine-command`. - The CLI maps each new kind to an `engine_command_<step>` fingerprint suffix. The category stays `runtime_crash`, so category-level grouping is unchanged. - The kind already travels as an open string on every owner/client boundary, so there is no wire or schema change. A client that doesn't know a new kind reports it as unclassified, as it does today for any unknown kind. ## Linked issue None. Telemetry follow-up to the stack failure classification in #7044. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary Scoped personal access tokens let a user pick exactly which permissions a token gets, so the CLI needs to say which permissions each command requires. This continues the mapping work from the command permission model; see [CLI-2474](https://linear.app/supabase/issue/CLI-2474/proposal-derive-and-document-the-management-api-scopes-each-cli). ## What's in this PR - Reusable permission building blocks for the Management API calls made by shared modules rather than command handlers: the DB config resolver, project ref picker, linked-project cache, link services, storage credentials, project API keys, upgrade suggestion, branch ref and target lookups, the login profile lookup, and linked service versions. Many commands reach these modules, so each is mapped once and included by the commands that use it instead of being repeated in every declaration. Each block lives next to the module whose calls it describes. - An integration test per block that runs the real module and checks the requests it makes against the block, so the mapping can't drift from the code. Command tests replace these modules with fakes, so they wouldn't catch it. Each best-effort call also has a 403 case showing the module's result survives, and the drift check verifies `request.query` qualifiers against the recorded requests. - Model additions the blocks needed: - `{ flag, is }` tests a switch by its effective value, so `--skip-pooler=false` is distinguished from omitting it. A flag given an empty value counts as absent. - `{ dbTarget }` conditions with `withDbTarget` select on the database a run targets: `--db-url`, then `--local`, then `--linked`, then the command's default. The DB config block applies only on the linked target. - `withKind` lets a command override a block's required or best-effort label. - `request.query` records request details such as `reveal=true` as data. The blocks live in shipped modules and run `block()` when those modules load, which adds a small amount of declaration data to the bundle and changes nothing else at runtime. No command includes them yet; later PRs map the commands that do. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…og tail (#7120) ## Summary The sync repair agent only received the last 150 lines of each failed CI job. Turbo interleaves package output, so on #7112 the failing CLI integration test landed mid-log, behind the `@supabase/stack` output, and the agent reported it could not see the failure. `failureExcerpt` now keeps 40 lines after every `##[error]` annotation and vitest `FAIL` header, plus the existing tail, with ANSI codes and timestamps stripped. Gaps are marked with `[...]`. ## Linked issue - [x] I'm a Supabase maintainer. ## Checklist - [x] The PR title follows Conventional Commits. - [x] Tests added or updated for the change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary Approving a sync PR required its branch to contain the latest source tip (`develop` for `sync/develop-into-next`). A sync PR stays open while `develop` keeps moving, so any new conflict sent it back to a human, as on #7112 (comment). - When only the source moved, the fast-forward job now lands the approved head that CI tested. - When the target moved, it merges only the latest target into the approved head. A conflict there still asks for re-approval. - Newer source commits arrive with the next `Sync branches` run, on the next push to the source. The release-process doc is updated to match. ### Before ```mermaid flowchart LR A[sync PR approved] --> T{next moved?} T -- no --> S{develop moved?} S -- no --> F[fast-forward next] S -- yes --> M T -- yes --> M{merge latest next + develop cleanly?} M -- yes --> L[land merged head] M -- no --> H[human resolves conflict, re-approves] ``` ### After ```mermaid flowchart LR A[sync PR approved] --> T{next moved?} T -- no --> F[fast-forward next to approved head] T -- yes --> M{merge latest next cleanly?} M -- yes --> L[land merged head] M -- no --> H[human resolves conflict, re-approves] F --> D[next develop push: Sync branches brings the rest] L --> D ``` ## Linked issue - [x] I'm a Supabase maintainer. ## Checklist - [x] The PR title follows Conventional Commits. - [x] Tests added or updated for the change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The macOS native-scenarios job is the longest job in the Test workflow, about twice any other. Most of it is `native: parallel stack isolation`, which starts two full native stacks at once and is starved on the standard `macos-latest` runner: it takes roughly 2x longer there than on the 8 vCPU Linux native job running the same test. Sharding the scenario files did not help (#7121), since Vitest already ran both files concurrently. This moves the job to the 6 vCPU Blacksmith macOS runner (`blacksmith-6vcpu-macos-latest`) so the two stacks get enough CPU and memory, keeping full macOS coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
| async (run) => | ||
| `## ${run.name} (${run.conclusion})\n\n\`\`\`\`text\n${await jobFailureExcerpt(token, repository, run.id)}\n\`\`\`\`\n`, |
There was a problem hiding this comment.
⚪ Severity: LOW
An untrusted test can print attacker-controlled instructions into this fixed four-backtick block; failureExcerpt preserves fence delimiters, and this file is loaded into Claude's editable context. The injected text can steer the repair agent to commit malicious source changes as a bot, which may reach next after maintainer approval.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Don't wrap the CI failure excerpt in a hardcoded four-backtick fence. Use the existing codeFence() helper, which picks a fence longer than any backtick run in the text, so untrusted test output can't close the block and inject instructions. Compute the excerpt once, then build the section with codeFence(excerpt, "text").
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| async (run) => | |
| `## ${run.name} (${run.conclusion})\n\n\`\`\`\`text\n${await jobFailureExcerpt(token, repository, run.id)}\n\`\`\`\`\n`, | |
| async (run) => { | |
| const excerpt = await jobFailureExcerpt(token, repository, run.id); | |
| return `## ${run.name} (${run.conclusion})\n\n${codeFence(excerpt, "text").join("\n")}\n`; | |
| }, |
…LI-2599) (#7115) ## Summary Stable now warns on stderr for every flag that CLI-2596 removes in v3. Each warning names the replacement and says the flag goes away in v3. | Flag | Warning | | -- | -- | | `domains create\|get\|reverify\|activate\|delete --include-raw-output` | `Flag --include-raw-output is deprecated and will be removed in v3, use -o json instead.` (new, previously help text only) | | `db pull --use-pg-delta` | `Flag --use-pg-delta is deprecated and will be removed in v3, use --declarative with [experimental.pgdelta] enabled = true in your config.toml instead.` (reworded) | | `db pull --experimental` (structured dump) | ``The --experimental structured-dump mode for `db pull` is deprecated and will be removed in v3. Use --declarative instead to pull the remote schema as per-object files.`` (reworded) | - The domains warning fires only when the flag is set. It goes to stderr, so `-o json` / stream-json stdout is unchanged. - There are no other behaviour changes. `--include-raw-output` still forces JSON output, and it is still inert on `delete`. - Flag-level usage over the last 30 days is low: `--include-raw-output` 2 runs from 1 person, `--use-pg-delta` 47/30, `--experimental` 17/9. The numbers are on CLI-2596. The removal itself is in a separate PR against `next`. ## Linked issue Closes CLI-2599 ## Checklist - [x] The PR title follows Conventional Commits. - [x] Tests added or updated: each domains command's integration test covers the warning with and without the flag, and the db pull tests assert the new wording. - [x] `pnpm check:all` passes. The apps/cli unit and integration suites pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…7106) ## Summary Scoped personal access tokens let a user pick exactly which permissions a token gets, so the CLI needs to say which permissions each command requires. This maps the first commands onto the permission model; see [CLI-2474](https://linear.app/supabase/issue/CLI-2474/proposal-derive-and-document-the-management-api-scopes-each-cli). ## What's in this PR - Permission declarations for `secrets list`, `snippets list` and `db query`, built from the shared building blocks (project ref picker, linked-project cache, DB config). `db query` selects its Management API call by database target, defaulting to local. These three work as the worked example for mapping the remaining commands. - Each command's integration test checks the requests it records against its declaration, so the mapping can't drift from the code. The tests also run the commands through the real linked-project cache layer, and `secrets list` through the project picker, so a block dropped from a declaration fails a test. - `assertBlockDrift`, a drift check for building blocks that takes its label from the block instead of a made-up command name. The existing block tests use it, and `permission-drift.ts` has callers now, so its `knip` entry is gone. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
`Run integration tests` ran every package's integration suite in one job, with the stack suite as the long pole. This turns it into a matrix: the stack suite in three Vitest shards, the CLI suite in two, and the API suite plus the stack namespace e2e tests in their own entry. The `Run unit and integration tests` summary gate still aggregates the matrix result. CLI e2e files run serially, so a shard can be no shorter than its longest file, and `start.lifecycle.e2e.test.ts` alone set that floor. Its two health-check scenarios move to `start.health.e2e.test.ts`, the e2e timings are refreshed from a current run, and CLI e2e runs in five shards instead of four. ## Effect on the Test workflow Compared with the same workflow on `develop`, excluding time spent waiting for a runner: - The slowest integration job is about 55% shorter, so integration tests are no longer on the critical path. - The slowest CLI e2e shard spends about 15% less time in tests. One shard still runs about 25% over the others, so the timing-based split is not yet even. - The workflow as a whole finishes about 20% sooner. It is now bounded by the CLI and stack e2e jobs, which end within a few seconds of each other. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
| readFileSync(join(PROMPTS_DIR, "resolve-prompt.md"), "utf8"), | ||
| "## Conflicted files", | ||
| "", | ||
| ...conflicts.map(({ path, kind }) => `- \`${path}\` (${kind})`), |
There was a problem hiding this comment.
⚪ Severity: LOW
A contributor-controlled filename from a conflicting PR is interpolated into Claude's resolution prompt; a newline or escaped backtick can break out of the file-list entry and inject instructions. The edit-capable agent may then commit malicious source changes to the sync branch, which can be promoted after approval.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Don't interpolate the raw filename into the prompt. Serialize it with JSON.stringify so newlines, control characters and quotes are escaped, and drop the backtick code-span wrapper that a crafted filename could break out of. Optionally, add a line to resolve-prompt.md saying that the file names listed are untrusted data and not instructions.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| ...conflicts.map(({ path, kind }) => `- \`${path}\` (${kind})`), | |
| ...conflicts.map(({ path, kind }) => `- ${JSON.stringify(path)} (${kind})`), |
… (#7117) ## TL;DR brings the `migration` module under the effect lint ## whats introduced? - the `migration*` and `migrate-and-seed` files join the allow list, with the sql helpers they run through - (`postgres-url`, `sql-split`, `param-introspection`, `pg-net-guidance`, `drop-objects`, `pg-dump` and `bundled-postgres-client`), and `postgres-client.run` widens to its tests - `checkScannerBufferSize` reads `SUPABASE_SCANNER_BUFFER_SIZE` through `Config`, still ahead of the project env - migration timestamps go through `DateTime`, and `migration fetch` yields its errors directly with the same messages - the migration and seed tests swap `node:fs` and `process.env` for scoped temp dirs and `withConfigEnv`, and the `pg-dump` and some `migration-history` tests move to `it.effect` ## ref - related: CLI-2654
CLI-2654) (#7118) ## TL;DR brings the `seed` and `pgdelta` modules under the effect lint ## whats introduced? - the `seed*`, `pgdelta*` and `diff-engine` files join the allow list - `isPgDeltaDebugEnabled` reads `PGDELTA_DEBUG` through `Config`, so `db diff`, `db pull` and the declarative `generate` and `sync` yield it - `seed-ops` fails the scanner check with `MigrationSeedError` instead of a bare `Error`, with the same message - `seed-buckets` yields its errors directly and recovers with `orElseSucceed` - the pgdelta tests pin `PGDELTA_DEBUG` through `withConfigEnv`, and the ssl probe test owns its server through `acquireRelease` ## ref - closes: CLI-2654
supabase_functionson the stack backend (CLI-2626) (fix(cli): createsupabase_functionson the stack backend (CLI-2626) #7029)stack destroyshould accept a list of stack ids #6964) (feat(stack): allow repeatable --stack-id in stack destroy (#6964) #7000)dbconnection string parser with effect lint (CLI-2653) (refactor(cli): cover thedbconnection string parser with effect lint (CLI-2653) #7045)dbconnections and dns with effect lint (CLI-2653) (refactor(cli): coverdbconnections and dns with effect lint (CLI-2653) #7046)dbconfig toml reader with effect lint (CLI-2653) (refactor(cli): cover thedbconfig toml reader with effect lint (CLI-2653) #7048)dbmodule with effect lint (CLI-2653) (refactor(cli): cover the rest of thedbmodule with effect lint (CLI-2653) #7049)startfailing on podman without snippets (CLI-2690) (fix(cli): stopstartfailing on podman without snippets (CLI-2690) #7089)api,httpandauthmodules with effect lint (CLI-2649) (refactor(cli): cover theapi,httpandauthmodules with effect lint (CLI-2649) #7084)project-environmentandlocal-project-contextwith effect lint (CLI-2650) (refactor(cli): coverproject-environmentandlocal-project-contextwith effect lint (CLI-2650) #7077)viper-envwith effect lint (CLI-2650) (refactor(cli): coverviper-envwith effect lint (CLI-2650) #7078)