Skip to content

chore: production deploy - #7104

Open
supabase-cli-releaser[bot] wants to merge 80 commits into
mainfrom
develop
Open

supabase-cli-releaser[bot] wants to merge 80 commits into
mainfrom
develop

Conversation

@supabase-cli-releaser

Copy link
Copy Markdown
Contributor

7ttp and others added 30 commits October 6, 2026 17:15
…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".


![stack-gateway-docker](https://gist.githubusercontent.com/avallete/718fe0d60eab6b1000f85175cd6242bf/raw/stack-gateway-docker.gif)

🤖 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["&lt;id&gt;/owner.lock<br/>SQLite lease = stack is live"]
        state["&lt;id&gt;/state.json<br/>registration and definitions"]
        data["&lt;id&gt;/data/&lt;instance&gt;<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
7ttp and others added 4 commits October 12, 2026 14:02
## 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>
@supabase-cli-releaser
supabase-cli-releaser Bot requested review from a team and supabase-oss as code owners October 13, 2026 02:37
@supabase-cli-releaser supabase-cli-releaser Bot added the do not merge Approve to apply; do not merge. label Oct 13, 2026
7ttp and others added 8 commits October 13, 2026 07:20
…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>
Comment thread .github/scripts/sync-agent.ts Outdated
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`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
`## ${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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
...capped(statLines, MAX_STAT_LINES),
...capped(statLines.map(sanitizeSubject), MAX_STAT_LINES),

avallete and others added 3 commits October 13, 2026 11:29
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>
Comment thread apps/cli/src/command-internal/link-services-core.integration.test.ts Dismissed
avallete and others added 3 commits October 13, 2026 12:56
…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>
Comment on lines +733 to +734
async (run) =>
`## ${run.name} (${run.conclusion})\n\n\`\`\`\`text\n${await jobFailureExcerpt(token, repository, run.id)}\n\`\`\`\`\n`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
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`;
},

Coly010 and others added 3 commits October 13, 2026 14:32
…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})`),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Suggested change
...conflicts.map(({ path, kind }) => `- \`${path}\` (${kind})`),
...conflicts.map(({ path, kind }) => `- ${JSON.stringify(path)} (${kind})`),

7ttp added 2 commits October 13, 2026 16:33
… (#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

This branch has not been deployed

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

Labels

do not merge Approve to apply; do not merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants