Skip to content

chore: production deploy - #6056

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

chore: production deploy#6056
supabase-cli-releaser[bot] wants to merge 19 commits into
mainfrom
develop

Conversation

@supabase-cli-releaser

Copy link
Copy Markdown
Contributor

7ttp and others added 19 commits July 31, 2026 13:08
## TL;DR

fixes `supabase start` failing with `EACCES on SELinux-enforcing hosts`
(Fedora + rootless Podman)
containers couldn't read the CLI-staged files under
`supabase/.temp/start-secrets/`
(Postgres's `pgsodium_root.key` first, then Kong/Supavisor secrets and
edge-runtime artifacts)

It was happening because of the missing SELinux relabel on those bind
mounts,
the files keep the workspace label, which a confined container can't
read despite the file mode.

So I've introduced a `Z` mount option for the three bind sites, which
fixes it up:
each file gets a private per-container label, sibling containers still
can't read them, and user project sources are never touched.
No-op without SELinux, and Docker/Podman both ignore ENOTSUP from
non-labelable filesystems, so nothing currently working changes...

## refs

- closes #5989
Follow-up to #5990
## **TL;DR** 
fixes `/auth/v1/*` (and any gateway route) returning 502 forever after
`supabase db reset`, which was happening because the reset restarts
auth/storage/
`realtime/pooler` but Kong's nginx keeps dialling their old cached
container IPs for the life of its process. Now sorted by running an
in-place `kong reload` after the satellite restarts (the same pattern
`functions serve` uses), so the gateway re-resolves the moved containers
while staying up the whole time

skipped cleanly, when Kong is absent/stopped, loud failure instead of a
silent broken gateway
otherwise. 

## ref: 
- closes #6016
…0 in /apps/cli-go in the go-minor group across 1 directory (#6023)

Bumps the go-minor group with 1 update in the /apps/cli-go directory:
[github.com/docker/go-connections](https://github.com/docker/go-connections).

Updates `github.com/docker/go-connections` from 0.7.0 to 0.8.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/go-connections/commit/754f9060ef9371a6e9504a82e25bd0bce0cfe406"><code>754f906</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/go-connections/issues/158">#158</a>
from thaJeztah/no_umask</li>
<li><a
href="https://github.com/docker/go-connections/commit/20f47a112d2119c502530055300c3ba272fa3e17"><code>20f47a1</code></a>
sockets: read somaxconn from system instead of SOMAXCONN</li>
<li><a
href="https://github.com/docker/go-connections/commit/e195e2a4e6e63b1ac25d4e1c2511170bdb030781"><code>e195e2a</code></a>
sockets: set socket permissions without umask hack</li>
<li><a
href="https://github.com/docker/go-connections/commit/32c72ec777e66c7f4a391097f400ea62eff7e63f"><code>32c72ec</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/go-connections/issues/162">#162</a>
from thaJeztah/abstract_sockets</li>
<li><a
href="https://github.com/docker/go-connections/commit/f3526e58848fc48baf375ca42aa23d0d18dafefa"><code>f3526e5</code></a>
sockets: improve abstract Unix socket handling</li>
<li><a
href="https://github.com/docker/go-connections/commit/fd93b41aeecfdee02fe3e7be3f8799c8842f8cef"><code>fd93b41</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/go-connections/issues/163">#163</a>
from thaJeztah/rm_log</li>
<li><a
href="https://github.com/docker/go-connections/commit/d0c75596e3ef03a6fb9e78befc9b18d9eed28e7e"><code>d0c7559</code></a>
sockets: update more tests to use tempSocketPath utility</li>
<li><a
href="https://github.com/docker/go-connections/commit/7106f49a36e292e9d0cad10f1842505304254cc4"><code>7106f49</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/go-connections/issues/161">#161</a>
from thaJeztah/todone</li>
<li><a
href="https://github.com/docker/go-connections/commit/fa1caa79d797b5a4d622aecc371dd91c677d7e32"><code>fa1caa7</code></a>
sockets: fix some remaining TODOs in Windows code</li>
<li><a
href="https://github.com/docker/go-connections/commit/31d55b210c6596022c9dd2a2a06093d62e086dac"><code>31d55b2</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/go-connections/issues/160">#160</a>
from thaJeztah/inmemory_context</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/go-connections/compare/v0.7.0...v0.8.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/docker/go-connections&package-manager=go_modules&previous-version=0.7.0&new-version=0.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the actions-major group with 3 updates:
[coverallsapp/github-action](https://github.com/coverallsapp/github-action),
[docker/login-action](https://github.com/docker/login-action) and
[linear/linear-release-action](https://github.com/linear/linear-release-action).

Updates `coverallsapp/github-action` from 2.3.7 to 2.3.8
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/coverallsapp/github-action/releases">coverallsapp/github-action's
releases</a>.</em></p>
<blockquote>
<h2>v2.3.8</h2>
<h2>What's Changed</h2>
<ul>
<li>README.md: Update GitHub Actions by <a
href="https://github.com/cclauss"><code>@​cclauss</code></a> in <a
href="https://redirect.github.com/coverallsapp/github-action/pull/259">coverallsapp/github-action#259</a></li>
<li>Spelling by <a
href="https://github.com/jsoref"><code>@​jsoref</code></a> in <a
href="https://redirect.github.com/coverallsapp/github-action/pull/258">coverallsapp/github-action#258</a></li>
<li>Fix macOS install for Homebrew 6.0.0 tap trust requirement by <a
href="https://github.com/afinetooth"><code>@​afinetooth</code></a> in <a
href="https://redirect.github.com/coverallsapp/github-action/pull/265">coverallsapp/github-action#265</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jsoref"><code>@​jsoref</code></a> made
their first contribution in <a
href="https://redirect.github.com/coverallsapp/github-action/pull/258">coverallsapp/github-action#258</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/coverallsapp/github-action/compare/v2...v2.3.8">https://github.com/coverallsapp/github-action/compare/v2...v2.3.8</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/coverallsapp/github-action/commit/8d6379e14d29928660c4ba802d8e85393440b329"><code>8d6379e</code></a>
Fix macOS install for Homebrew 6.0.0 tap trust requirement (<a
href="https://redirect.github.com/coverallsapp/github-action/issues/265">#265</a>)</li>
<li><a
href="https://github.com/coverallsapp/github-action/commit/0a51d2e0b5417d06e4ecceb534aec87defc53926"><code>0a51d2e</code></a>
Spelling (<a
href="https://redirect.github.com/coverallsapp/github-action/issues/258">#258</a>)</li>
<li><a
href="https://github.com/coverallsapp/github-action/commit/dc7137bf2369a87af98c6f88645f0a46685e68d5"><code>dc7137b</code></a>
README.md: Update GitHub Actions (<a
href="https://redirect.github.com/coverallsapp/github-action/issues/259">#259</a>)</li>
<li><a
href="https://github.com/coverallsapp/github-action/commit/ba6dae83310bbd6d33ff77092650dc40bd444a2a"><code>ba6dae8</code></a>
Revise README for clarity on integrations and support</li>
<li><a
href="https://github.com/coverallsapp/github-action/commit/a5a505eafd8c7e40bae6eb42aa08b5a0eff9b70b"><code>a5a505e</code></a>
Update README with new sections and information</li>
<li>See full diff in <a
href="https://github.com/coverallsapp/github-action/compare/5cbfd81b66ca5d10c19b062c04de0199c215fb6e...8d6379e14d29928660c4ba802d8e85393440b329">compare
view</a></li>
</ul>
</details>
<br />

Updates `docker/login-action` from 4.5.1 to 4.5.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/login-action/releases">docker/login-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.5.2</h2>
<ul>
<li>Surface Docker Hub OIDC error responses by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/login-action/pull/1058">docker/login-action#1058</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/login-action/compare/v4.5.1...v4.5.2">https://github.com/docker/login-action/compare/v4.5.1...v4.5.2</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/login-action/commit/371161bbe7024a29a25c5e19bfcbc0804fe9ad2c"><code>371161b</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/login-action/issues/1058">#1058</a>
from crazy-max/fix-dockerhub-oidc-error-handling</li>
<li><a
href="https://github.com/docker/login-action/commit/5dc73df38ebcfa6f96479901e253d172c3e35849"><code>5dc73df</code></a>
chore: update generated content</li>
<li><a
href="https://github.com/docker/login-action/commit/2aa1edee0b06c23880529064a4f7d7d3d2f9bc87"><code>2aa1ede</code></a>
surface Docker Hub OIDC error responses</li>
<li>See full diff in <a
href="https://github.com/docker/login-action/compare/abd2ef45e78c5afb21d64d4ca52ee8550d9572c7...371161bbe7024a29a25c5e19bfcbc0804fe9ad2c">compare
view</a></li>
</ul>
</details>
<br />

Updates `linear/linear-release-action` from 0.14.6 to 0.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/linear/linear-release-action/releases">linear/linear-release-action's
releases</a>.</em></p>
<blockquote>
<h2>v0.15.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Release v0.15.0 by <a
href="https://github.com/RomainCscn"><code>@​RomainCscn</code></a> in <a
href="https://redirect.github.com/linear/linear-release-action/pull/55">linear/linear-release-action#55</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/linear/linear-release-action/compare/v0.14.6...v0.15.0">https://github.com/linear/linear-release-action/compare/v0.14.6...v0.15.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/linear/linear-release-action/commit/af56a9a388625921f3757a2f988e4d7aca958377"><code>af56a9a</code></a>
Release v0.15.0 (<a
href="https://redirect.github.com/linear/linear-release-action/issues/55">#55</a>)</li>
<li>See full diff in <a
href="https://github.com/linear/linear-release-action/compare/3858a5d7892435dc63302ac76b0cdb587435caa9...af56a9a388625921f3757a2f988e4d7aca958377">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This PR was automatically created to sync API types from the
infrastructure repository.

Changes were detected in the generated API code after syncing with the
latest spec from infrastructure.

Co-authored-by: supabase-cli-releaser[bot] <246109035+supabase-cli-releaser[bot]@users.noreply.github.com>
## What changed

`sso add` emitted a hand-written mutual-exclusivity error (`only one of
--metadata-file or --metadata-url may be set`) and detected the conflict
via `Option.isSome` on parsed flag values. The Go CLI enforces this
group via cobra's `MarkFlagsMutuallyExclusive("metadata-file",
"metadata-url")` (`apps/cli-go/cmd/sso.go:164`), whose error template
is:

```
if any flags in the group [metadata-file metadata-url] are set none of the others can be; [metadata-file metadata-url] were all set
```

`sso update` was already migrated to the shared
`cobraMutuallyExclusiveErrorMessage` helper plus raw-argv
`pflag.Changed`-semantics detection (CLI-1902); `add` was never
migrated. This PR mirrors update's pattern in `add.handler.ts`:

- Byte-exact cobra template via `cobraMutuallyExclusiveErrorMessage`
(group in Go's registration order; the violating subset sorted, per
cobra's `validateExclusiveFlagGroups`).
- `hasExplicitValueFlag` raw-argv scan, so an explicit empty value
(`--metadata-file= --metadata-url x`) still trips the mutex, while a
bare `--metadata-file --metadata-url` (pflag consuming the second token
as the first flag's value) correctly does not.
- The check runs before project-ref resolution, matching cobra's
`ValidateFlagGroups`-before-`RunE` precedence.

Integration tests cover the exact-message case (byte-match), the
explicit-empty `--metadata-file=` case, the consumed-value non-violation
case, and single-flag happy paths. `SIDE_EFFECTS.md` now documents the
cobra template and `Changed` semantics.

## Review findings deliberately left open

A four-perspective review pass (architect / engineer / security / DX)
approved the change; these pre-existing, cross-cutting observations were
noted rather than fixed here:

- The mutex filter/fail orchestration is now duplicated between `sso
add` and `sso update` — a family-root helper (e.g. `sso.mutex.ts`) is a
reasonable follow-up.
- `SSO_ADD_VALUE_FLAG_NAMES` (like update's equivalent) is a
hand-maintained mirror of the command's declared value flags with no
compile-time sync guarantee.
- Telemetry flushes on a mutex violation, whereas Go's cobra fails
flag-group validation before telemetry is installed — pre-existing
divergence shared with `sso update`.
- The raw-argv scan doesn't understand global/inherited value flags or
the `-t` shorthand — documented limitation shared with `sso update`;
pflag fails `-t`'s enum validation before flag groups anyway.
- The `--domains=` explicit-empty edge on `add` (parity audit §3.10) is
a separate issue and is not addressed here.

Fixes CLI-1982


https://linear.app/supabase/issue/CLI-1982/sso-add-mutual-exclusivity-error-is-not-cobra-format
…LI-1990) (#5978)

Batch sweep of the small confirmed edge/cosmetic Go-parity divergences
from the 2026-07-24 audit. Every item was re-verified against
`apps/cli-go` at current develop before changing anything (audit line
numbers were stale); several items turned out to be already fixed or
in-flight in other PRs and are listed as such.

Fixes CLI-1990 —
https://linear.app/supabase/issue/CLI-1990/edgecosmetic-parity-sweep-from-the-2026-07-24-audit-batchable-tail

## Item-by-item

| # | Item | Status | Notes |
|---|------|--------|-------|
| 1 | `functions delete` line colour | **Fixed** | Aqua slug + ref,
stdout-gated (`delete.go:20`) |
| 2 | `functions deploy` success ref Aqua, `Bundling Function:` bold,
`No Functions specified…` bold | **Fixed** | `deploy.go:70,35`,
`bundle.go:30`; stdout-gated where stdout-bound |
| 3 | Prune bullets ` • <bold>` | **Already fixed** | by #5947
(CLI-1974), commit `c4b45874` |
| 4 | serve `supabase start is not running.` Aqua | **Deferred** | open
PR #5976 modifies the same `assertLocalDbRunning` hunk in
`shared/functions/serve.ts`; one-liner to do after it merges |
| 5 | `encryption update-root-key` Finished line + bogus comment |
**Fixed** | Aqua'd; comment claimed a nonexistent "render Aqua as plain"
convention |
| 6 | start rollback missing `Stopping containers...` | **Fixed** |
stderr, matching Go's `DockerRemoveAll` writer on the start-failure path
(`start.go:77`) |
| 7 | `--debug` `Pruned …` reports | **Fixed** | `Pruned
containers:/volumes:/network:` (singular network) `[a b c]` on stderr;
prune stdout now collected (also removes a latent unread-pipe hazard);
`LegacyDebugFlag` threaded through stop/start/rollback |
| 8 | Per-retry `Retrying after Ns: <image>` banner | **Fixed** |
`4s`/`8s` per Go's `2<<(i+1)` schedule (`docker.go:314`); the failed
attempt's error line is played by the already-teed `docker pull` stderr
|
| 9 | `inspect db blocking` backtick `blocking_statement` | **Fixed** |
col 2 only; col 5 (`blocked_statement`) stays bare per Go's format
string (`blocking.go:56`) |
| 10 | `seed buckets` mutex bracket `[local linked]` | **Fixed** | cobra
keeps registration order for the group list and sorts only the "were all
set" list; corrected the misleading comment in
`legacy-db-target-flags.ts` (storage's `[linked local]` stays correct) |
| 11 | `snippets download` 4 UUID forms + lowercase URL | **Fixed** |
faithful `uuid.Parse` port incl. the braced-form `s[1:]` quirk;
canonical lowercase interpolated into the URL; Go's three error branches
verbatim |
| 12 | `storage cp --jobs` negative rejection | **Fixed** | pflag's
exact uint error (`invalid argument "-1" for "-j, --jobs" flag:
strconv.ParseUint: …`), before mutex validation and without telemetry;
the documented `0 → 1` clamp stays (Go's 0 deadlocks) |
| 13 | `postgres-config` value coercion + `%+v` floats | **Fixed** |
exact `ParseBool` case set; int64-overflow → verbatim string; pretty
table renders numbers with Go's float64 `%g` (`1000000` → `1e+06`) via
hoisted `legacyGoFormatFloat` (also reused by `db query`) |
| 14 | init template file modes | **Fixed** | 0644/0755 pinned; the
gitignore *append*-branch write is deliberately left unpinned — mode
only applies at creation, and #5977 rewrites that exact line |
| 15 | login fallback dir 0700→0755 | **Fixed** | Go pins the dir 0755
(`access_token.go:91`); the token file stays 0600, so no secret exposure
|
| 16 | `test new` 0644 | **Fixed** | + dir 0755, like Go's
`utils.WriteFile` |
| 17 | `inspect report` 0755/0644 | **Fixed** | |
| 18 | bootstrap invalid-stored-token fast-fail | **Deferred** | not
small: `resolveLegacyAccessToken` deliberately collapses invalid→`None`
for many callers (sso, snippets, postgres-config, …); distinguishing
invalid-vs-missing needs a shared-semantics change |
| 19 | `migration new` Created line on stdin-copy failure | **Fixed** |
mirrors Go's deferred `Println` (`new.go:24-28`); also stdout-gates the
Bold path (CLI-1546 class) |
| 20 | telemetry state-file recovery | **Fixed** | all-or-nothing decode
like Go's `decodeState`; a corrupt file resets `enabled` to true and
rotates identity, exactly like Go |
| 21 | unlink/services ref-read error | **Fixed (services)** | unlink
already matched Go; services now warns `failed to load project ref: …`
on a read error and continues unlinked (TOCTOU NotFound stays silent,
like Go's `ErrNotLinked` branch) |
| 22 | `domains` CNAME dump byte codes | **Kept documented** | premise
inaccurate: the non-reproducible part of Go's `%+v` is a runtime
*pointer address* for `ValidationErrors`, not byte codes; TS's
deterministic rendering is already documented in `domains.format.ts` |
| 23 | sso `--domains=` explicit-empty | **Deferred** | `sso update`
already matches Go (len-check drops it); the `add` fix touches
`sso/add/add.handler.ts`, in-flight in open PR #5974 |
| 24 | `db dump --file ""` | **Fixed** | keys off `len > 0` like Go:
empty means stdout, no file open, no `Dumped schema to …` line |
| 25 | network-restrictions CIDR-before-ref ordering | **Deferred, kept
documented** | direction is inverted vs the issue text: TS validates
CIDR *before* ref, Go resolves ref first in `PersistentPreRunE`;
aligning overlaps open PR #5975 (incl. its integration test file). The
`SIDE_EFFECTS.md` note frames this as intended based on an incomplete Go
reading — worth revisiting after #5975 |

## Known residuals (deliberate, documented in code)

- `postgres-config` digits in `(2^53, 2^63)` still lose precision on the
way in (`JSON.stringify` cannot emit exact int64 tokens); Go sends exact
integers there. Values beyond int64 now match Go (string fallback).
- Colour TTY gating: stderr-bound colour gates on stderr's TTY (per
`legacy-colors.ts`/CLI-1546 convention), whereas Go's lipgloss gates
everything on stdout. Deliberate, pre-existing convention; only
observable when exactly one of stdout/stderr is a TTY.
- Bun's `util.styleText` currently ignores `validateStream`/`NO_COLOR`
(verified on Bun 1.3.x), so under Bun piped output still carries ANSI
for *all* legacy colour sites — a pre-existing runtime gap that predates
this PR and deserves its own issue.
- The services warning's error suffix is Effect's error text, not Go's
`*PathError` bytes — the `failed to load project ref: ` prefix is the
parity-bearing part.
- `--jobs abc`/`3.5` still surface Effect CLI's parser error rather than
pflag's; this PR scopes to negatives (the only case `Flag.integer`
accepts that Go rejects).

## Review notes

Four-perspective review (architect / engineer / security / DX) run
pre-PR; all approve. Engineer fuzz-verified `legacyGoFormatFloat` (23k
values) and `legacyParseSnippetUuid` (~8k inputs) byte-identical to
Go/google-uuid. Security signed off on the 0700→0755 fallback-dir change
(token file unchanged at 0600, matches Go exactly). Remaining findings
were the documented residuals above.
…6005)

Two `functions deploy` divergences from the pinned Go CLI
(`apps/cli-go`), resolved per the CLI-1985 ruling (Colum, 2026-07-30:
take each point's documented recommended option).

Fixes CLI-1985

## ⚖ Parity ruling applied

### Point 1 — `NPM_AUTH_TOKEN` is no longer forwarded into the Docker
bundler (strict parity; **breaking** for private-registry users)

**Decision:** remove the forwarding. The eszip bundler container now
receives only `NPM_CONFIG_REGISTRY` from the host, exactly matching Go
(`apps/cli-go/internal/functions/deploy/bundle.go:68-70`).

**Evidence:**
- The Go CLI never forwarded `NPM_AUTH_TOKEN` at any point in its
history — only `NPM_CONFIG_REGISTRY` (added in `8e17f033`).
- The Go-side PR proposing the token forwarding (#4933, addressing
#4927) was **closed unmerged** on 2026-06-22 ("The command is now ported
in TypeScript so I'm closing this PR").
- The TS-only forwarding came from #5645, which ported the unmerged
#4933. CLI-1985 ruled strict parity over that TS-only addition.

**User-visible change (flagging prominently):** users whose `.npmrc`
expands `${NPM_AUTH_TOKEN}` for private npm registries will find
`--use-docker` / `--legacy-bundle` deploys failing registry auth again
(the pre-#5645 and Go CLI behavior; re-opens the CI/CD-host case of
#4927). Workarounds: inline the token in `.npmrc`, or deploy via the
default `--use-api` path. Per the strict-parity contract (stderr bytes
included), no TS-only warning was added when the variable is set — a DX
reviewer requested one and it was rejected on parity grounds; the
breaking impact is documented here and in the commit message instead.

**Shared-code caveat (per the ruling):** `dockerNpmEnv` lives in
`apps/cli/src/shared/functions/deploy.ts` and serves **both** shells —
`next/` (`functions deploy`) and `legacy/`. The removal therefore
applies to the next/ shell too. The strict-parity contract only binds
the legacy shell, but keeping one code path is the simplest correct
design per repo policy, so next/ loses the forwarding as well — stated
here explicitly. `functions serve` is unaffected (it has its own env
handling, matching Go's serve which loads `supabase/functions/.env`).

### Point 2 — API-deploy upload paths re-anchored at the workdir (align
to the pinned oracle; behaviour change)

**Directive:** confirm the intended reference point first, then align or
record.

**Evidence found:**
- Upstream Go **never** anchored deploy paths at the git root. The full
history of `pkg/function/deploy.go` (pre- and post-monorepo move) shows
`toRelPath` anchored at `os.Getwd()` since `29021998` ("convert all
paths to relative for deploy", #3403), unchanged since. The Go CLI
chdirs to the workdir (`internal/utils/misc.go:238`), so `os.Getwd()` ≡
the workdir.
- The TS git-root anchoring came from #5755 (merged 2026-07-02), a
deliberate TS-side monorepo fix closing #3467 (Go hard-fails on imports
outside the workdir: `failed to read file: open ../common/index.ts`) —
**not** a port of newer upstream Go behavior. There is no newer upstream
Go reference to record against.

**Decision (per the ruling's matrix — upstream never did this →
align):** uploaded multipart file names and the server-recorded
`entrypoint_path` / `import_map_path` / `static_patterns` are now
anchored at the workdir with Go's exact `toRelPath` semantics (relative
to `os.Getwd()`, forward slashes, `../`-relative when the file lies
outside the workdir).

**Scope note:** #5755's import-walk *containment boundary* (which files
may be uploaded at all) is intentionally **kept** at the nearest git
root. The boundary is a TS-only safeguard with no Go equivalent — Go's
walker uploads any reachable import unbounded (and then hard-fails
opening `..` paths through `afero.NewIOFS`, which is exactly bug #3467).
Reverting the boundary would re-break #3467 and is outside CLI-1985's
anchoring scope.

**User-visible change:** in monorepos where the git root is an ancestor
of the workdir, redeploys now record
`supabase/functions/<slug>/index.ts` (matching what the Go CLI records
and the dashboard shows for Go deploys) instead of
`apps/myapp/supabase/functions/<slug>/index.ts`. Imports outside the
workdir but inside the git root still deploy, uploading with Go-style
`../`-relative names — the same name shape Go's `toRelPath` emits, so
nothing new is required of the server. Non-monorepo projects (git root
== workdir, the common case) are byte-for-byte unchanged.

## What changed

- `apps/cli/src/shared/functions/deploy.ts` — `dockerNpmEnvNames`
trimmed to `NPM_CONFIG_REGISTRY`; `deployViaApi` now threads the workdir
as the path anchor through `uploadFunctionSource` /
`writeSourceDeployForm` / `createSourceMetadata` while the git-root
`sourceRoot` remains the containment boundary; ENOENT warn display paths
follow the workdir anchor (matching Go's workdir-relative walker paths).
Docker bind construction is untouched.
-
`apps/cli/src/legacy/commands/functions/deploy/deploy.integration.test.ts`
— new regression test: workdir≠git-root monorepo deploy asserts
workdir-anchored metadata, `../`-relative upload names, and the
Go-parity `Uploading asset` stderr line.
-
`apps/cli/src/next/commands/functions/deploy/deploy.integration.test.ts`
— the two git-root upload tests updated to the workdir anchoring; the
npm env test now asserts `NPM_CONFIG_REGISTRY` is forwarded and
`NPM_AUTH_TOKEN` is not.
- `apps/cli/src/legacy/commands/functions/deploy/SIDE_EFFECTS.md` — env
table states only `NPM_CONFIG_REGISTRY` is forwarded; new note documents
the workdir anchoring and the TS-only git-root boundary.

All four changed/added tests fail against the previous implementation
and pass with this change.
…from Go (CLI-1987) (#6007)

## ⚖ Parity ruling applied (CLI-1987, Colum, 2026-07-30)

**Option (b) chosen: keep the TypeScript behaviour and document the
intentional divergence. Option (a) — matching Go's quirk — was rejected.
No runtime behaviour changes in this PR.**

Go's `start.IsUnhealthyError`
(`apps/cli-go/internal/db/start/start.go:227-231`) classifies **any**
`errors.Join`-shaped error as "unhealthy". That shape check accidentally
also matches `ensureImagesCached`'s `errors.Join(result...)`
(`apps/cli-go/internal/start/start.go:257-260`), so in Go, with
`--ignore-health-check` set, a total image-pull failure — or a Docker
daemon that becomes unreachable during the pre-pull — is **swallowed**:
Go prints the error, skips rollback, prints `Started supabase local
development setup.` + the status table + the security notice, and
**exits 0** with zero containers running. Go's own comment on
`IsUnhealthyError` ("Health check always returns a joinError") shows the
wider match was never intended.

The TS port already behaves differently — `legacyIsUnhealthyStartError`
matches only `LegacyHealthCheckTimeoutError`, and the image pre-pull
runs before the downgrade envelope — so the same scenario **exits 1 with
no success banner and no status table**. Per the ruling, that behaviour
is kept and is now documented + regression-pinned.
`--ignore-health-check` downgrades health-check timeouts only.

## What changed

Docs, comments, and one regression test — the runtime is untouched:

- `apps/cli/src/legacy/commands/start/start.rollback.ts` — divergence
record in `legacyIsUnhealthyStartError`'s doc comment, including an
explicit "do not fix this by widening the match toward Go's shape check"
guard, so a future parity sweep can't silently reintroduce Go's exit-0
swallow.
- `apps/cli/src/legacy/commands/start/SIDE_EFFECTS.md` — the image-pull
exit-code row now states the failure stays fatal even with
`--ignore-health-check`, and a new "Notes" entry records the full
carve-out (scenario, Go's quirk behaviour, TS behaviour, why rollback is
*not* part of the divergence — nothing has been created yet in either
CLI — and that the flag's Go-byte-matched help text "Ignore unhealthy
services and exit 0" over-promises here).
- `apps/cli/docs/go-cli-porting-status.md` — the legacy `start` entry
carries the same intentional-divergence note (the table's column padding
was re-flowed by `oxfmt`; the substantive change is the `start` row
only).
- `apps/cli/src/legacy/commands/start/start.integration.test.ts` — new
test in the "image pull" block: pre-pull exhaustion under
`--ignore-health-check` still fails with `LegacyImagePrepullError`,
prints no `Started` banner, emits nothing on stdout (no status table),
creates no container, and triggers no rollback. The daemon-unreachable
trigger funnels through the same `LegacyImagePrepullError` path, so the
one scenario pins both documented triggers.

## Overlap with CLI-1967

CLI-1967's doc-drift sweep also touches `start` documentation. The
CLI-1987 carve-out (SIDE_EFFECTS "Notes" entry, exit-code row,
porting-status `start` row, `start.rollback.ts` comment) is fully
handled **here** — CLI-1967 should not re-document this divergence.

## Possible follow-up (not in scope here)

Self-review flagged a DX gap that would require a runtime change, so it
is deliberately not part of this docs-only ruling PR: when
`--ignore-health-check` is set and the pre-pull fails, the error output
never explains why the flag didn't apply (and the flag's help text says
"exit 0"). A TS-only `error.suggestion` ("--ignore-health-check only
downgrades health-check timeouts; image pull failures are always fatal")
on that path — analogous to the existing TS-only `exec format error`
suggestion — would close it.

Fixes CLI-1987
…tible statements (#6009)

## What kind of change does this PR introduce?

Parity-ruling record + pinned-Go oracle alignment for `db push` / `db
reset` / `migration up` pipeline-incompatible statement handling.

Fixes CLI-1989

## ⚖ Parity ruling applied (Colum, 2026-07-30)

The parity audit flagged that the TS migration apply
(`apps/cli/src/legacy/shared/legacy-migration-apply.ts`) runs
pipeline-incompatible statements (`CREATE [UNIQUE] INDEX CONCURRENTLY`,
`REINDEX … CONCURRENTLY`, `VACUUM`, `ALTER SYSTEM`, `CLUSTER`)
standalone outside the batch transaction, while the pinned Go reference
(`apps/cli-go/pkg/migration/file.go`) had no such handling — a migration
containing `CREATE INDEX CONCURRENTLY` failed under pinned Go (SQLSTATE
25001) but succeeded under TS.

**Ruling:** confirm the intended upstream reference, record it, and
update the pinned `apps/cli-go` to match so future audits don't re-flag
this. The TS behaviour is **not** reverted.

### Confirmed provenance

- **Bug:** #5139 — `db reset` fails with SQLSTATE 25001
("CREATE INDEX CONCURRENTLY cannot be executed within a pipeline").
- **Reference design:** PR #5156 by @wucm667 —
`isPipelineIncompatible` / `trimLeadingSQLComments` +
flush-then-run-standalone in Go's `ExecBatch`. **Closed WITHOUT
merging** on 2026-06-24: there is no merged upstream Go commit. The
closing comment adopted the design directly into the TS port instead
(PR-branch commit 29d3fb0, part of #5671, squash-merged to develop as
b48fad6) because the Go path was being retired for the migration
commands.

### TS vs. reference semantics

Compared statement-for-statement against the #5156 diff: the TS
classification patterns, comment/BOM trimming, flush-then-standalone
execution order, final-batch history insert, and global statement-index
accounting in `At statement: N` errors all match. The only deltas are
regex-engine trivia (Go RE2 `\s`/`\z` vs JS `\s`/`$`) with no observable
effect on valid SQL — no TS behaviour change was needed.

### What this PR does

1. **Pinned Go updated** (`chore(cli-go)` commit): the #5156 diff
applies cleanly and is self-contained (`pkg/migration/file.go` + tests,
additive `pkg/pgtest` simple-query mock helpers; no dependency or API
churn), so it is applied **verbatim** to `apps/cli-go`. `go test
./migration/...` in `apps/cli-go/pkg` and the full main-module `go test
./...` pass; golangci-lint adds no new findings (the 5 pre-existing
gosec findings in `internal/utils/*` are untouched).
2. **Provenance recorded** (`docs(cli)` commit): the provenance note in
`legacy-migration-apply.ts`, the `db push` / `db reset` / `migration up`
SIDE_EFFECTS.md (including the non-atomic flush-boundary semantics: a
mid-file failure leaves earlier batches committed with no history row,
so a re-run replays the file from the top — prefer `… IF NOT EXISTS`
forms), and the `db push` / `db reset` / `migration up` rows in
`docs/go-cli-porting-status.md`.
3. **Classifier test parity** (`test(cli)` commit): the TS
`legacyIsPipelineIncompatible` suite claimed to mirror Go's
`TestIsPipelineIncompatible` but was missing two of its negatives
(string literal, leading-comment-only); those plus BOM /
unterminated-comment edge cases are added. 40/40 pass.

### Shipped-sidecar behaviour note

`apps/cli-go` is not just an audit-only reference — it is compiled into
the shipped `supabase-go` sidecar (`build:go-sidecar` in
`apps/cli/package.json`; `go build -o supabase-go` in CI). Local `db
start` and local `db reset` delegate migration apply to that sidecar via
the hidden `db __db-bootstrap` seam, which calls `ExecBatch` in
`pkg/migration/file.go`. So the pinned-Go update in this PR also extends
the #5139 `CREATE INDEX CONCURRENTLY` / pipeline-mode fix to that
delegated local path: a migration with `CREATE INDEX CONCURRENTLY`
previously failed under local `db start`/`db reset` with SQLSTATE 25001
and now succeeds, matching the already-working remote TS path. This is
filed as `chore` because it's realigning the pinned parity oracle rather
than introducing new intentional TS behaviour, but it's fair to note it
arguably deserves `fix` framing given it's a real shipped runtime
behaviour change on the local path, not purely an audit fix. A follow-up
`docs(cli)` commit (5a7f833) adds a one-line note on `ExecBatch`'s
definition recording this dual-path (remote TS + local sidecar) reach,
and a note on the known `\v`/Unicode-whitespace classification delta
between JS `\s` and Go's RE2 `\s` (a residual valid-SQL divergence:
PostgreSQL >= 14 treats `\v` as SQL whitespace but Go RE2 `\s` doesn't
match it, so e.g. `VACUUM\v(FULL)` classifies as pipeline-incompatible
in TS but not under the Go oracle).

Follow-up candidates (not in this PR): an error-message hint when a
standalone statement fails mid-file, and a user-facing docs-site note
about `CONCURRENTLY` migration best practice.
…void cross-process cache races (#6003)

## What kind of change does this PR introduce?

Bug fix.

## What is the current behavior?

`packages/stack/src/BinaryResolver.ts` caches downloaded native service
binaries (postgres, postgrest, auth, edge-runtime) at a path that is
intentionally shared across every process on the machine, so parallel
`supabase start` invocations don't re-download the same binary. That
sharing is correct, but there was no concurrency protection around it:

- The cache-hit check was check-then-act with no lock, so two processes
could both observe a cold cache for the same service+version at the same
instant.
- Both processes then downloaded to the **same fixed temp file path**
(`_download.tar`/`_download.zip`, no per-invocation uniqueness), so
concurrent writes could corrupt each other's bytes.
- Extraction (`tar`/`unzip`) ran directly into the final cache directory
rather than a private staging location, so an interrupted extraction
(this race, a killed process, disk pressure) could leave the directory
partially populated.
- The cache-hit check only looked at `entries.length > 0`, so a
partially-extracted, broken directory looked exactly like a valid cache
hit to every future invocation — silent, persistent corruption.

## What is the new behavior?

Downloads now write to a per-invocation-unique temp file, and extraction
happens in a per-invocation-unique staging directory
(`${cacheDir}.tmp-<uuid>`) sibling to the real cache directory instead
of the cache directory itself. Once extraction, the chmod fixup, and (on
macOS) ad-hoc codesign all succeed, the staging directory is atomically
renamed into place as the final cache directory — so the cache directory
is now only ever observable in a fully-complete state, and the existing
"empty directory looks like a cache hit" bug can no longer be produced
by this code path going forward.

If another process already published the cache directory by the time
this process tries to publish its own (i.e. it lost the race), it
discards its own staging directory and resolves to the winner's cache
entry instead of failing. The staging directory is also cleaned up on
every failure path (download error, checksum mismatch, extraction
failure, or interruption), so no `.tmp-*` directories are left behind in
the cache root.

Added a regression test in `BinaryResolver.unit.test.ts` that runs two
concurrent `resolveWithMetadata` calls against the same
service+version+assetName cache path (with mocked
`HttpClient`/`ChildProcessSpawner`/`FileSystem` layers) and asserts both
succeed to the same complete cache path with no stray temp artifacts
left behind.
## TL;DR

`supabase start` dying on Podman with `failed to create volume: ...
already exists`. <br>It never happened in Go because Go called the
Docker **Engine API**, <br>which is idempotent for a repeated volume
name (and stays idempotent against Podman, whose Docker-compat endpoint
just hands back the existing volume). After the port we shell out to the
**container CLI** instead, and `podman volume create` goes through
libpod rather than that compat endpoint, which rejects a repeated name
outright. <br>Fixed by treating an already exists rejection as success
in `legacyEnsureStartVolume`, the same way `legacyEnsureStartNetwork`
right above it already does, plus unit + integration tests.

## Why it kept biting

Named volumes survive `stop` unless `--no-backup`, so every
`stop`/`start` cycle re-creates volumes that were kept on purpose:

```
failed to create volume: Error: volume with name supabase_db_xxx already exists: volume already exists
```

`docker volume create` is unconditionally idempotent, so only Podman
hosts ever reached this branch.

## refs:

* closes #6020
…sition (#6004)

## What kind of change does this PR introduce?

Test only. Adds
`packages/stack/tests/postgresDataPersistence.e2e.test.ts`, a new e2e
test.

## What is the current behavior?

`packages/stack` can run Postgres either natively as a binary process or
as a Docker
container, chosen by `StackConfig.mode`. Both paths mount/set `PGDATA`
to the same
`dataDir`, and the design intent is that a stack stopped while running
natively can be
restarted in Docker mode against the same `dataDir` without losing data.
This had never
been tested end-to-end, and there was specific reason for suspicion: the
Docker
entrypoint in `packages/stack/src/services/postgres.ts` execs
`postgres -D /etc/postgresql -p ${port}` — a different path than the
`/var/lib/postgresql/data` volume mount target — so it was unverified
whether the two
actually resolve to the same data inside the `supabase/postgres` image.

## What is the new behavior?

The new test starts a stack in native mode, writes a marker row via
`Bun.SQL` directly
against Postgres, disposes the stack (explicit `dataDir`s are never
auto-cleaned, per
`cleanup.ts`), then starts a second stack in Docker mode against the
same `dataDir` and
verifies:
- the first stack really ran postgres as a native process (no matching
Docker container)
- the second stack really ran postgres as a Docker container
- the marker row written natively is still present and unchanged after
the transition

Ran repeatedly (including a sanity check that points the Docker stack at
a *different*
fresh `dataDir`, which reproducibly fails with `relation
"public.persistence_marker" does
not exist`, confirming the assertion has real detection power): the
persistence
assertion passes consistently. The `/etc/postgresql` vs
`/var/lib/postgresql/data` path
mismatch does not break persistence in practice — data written natively
is correctly
visible after switching to Docker mode against the same `dataDir`.
## TL;DR

Fixing `db push` failing with `Remote migration versions not found in
local migrations directory`
for a version that's sitting right there on disk, which happened because
local files arrive in **name** order while `schema_migrations` comes
back in **version** order, and
those two disagree whenever one version is a string prefix of another
(`1` vs `10`, or `20260420` vs `20260420010000`): `10_b.sql` sorts
before `1_a.sql` (`'0'` < `'_'`),
so the two-pointer merge desynchronises and reports an already-applied
version as missing. `migration up` walks the same merge, and `migration
repair --status reverted` is no way out, the versions just come back as
`ErrMissingRemote`...

 sorted now by ordering local paths by version before the walk: 
a new `legacySortMigrationPathsByVersion` called from both
`legacyFindPendingMigrations` implementations,
rather than from `legacyListLocalMigrations` where the name ordering
originates that list also feeds the `pgdelta` cache hash, so reordering
it there would drift the cache key. TS shell only, since that's the
user-facing path today via legacy....

`--include-all` needed the same treatment: 
it slices the local list at `remoteCount + diff.length`, so with `diff`
now version-ordered it has to index the version-ordered list too. Left
name-ordered it would re-apply an already-applied migration and silently
skip a pending one (`1,2,20` with `2` applied → `[1, 2]` instead of `[1,
20]`).

## Refs

- Closes #6036
## **TL;DR**

fixes the recurring red e2e shards (`docker: toomanyrequests: Rate
exceeded`):
raw `docker run`s implicit-pulled uncached images from a single
registry,
so one rate-limit killed the shard.

Now `ensureImage()` (tests/helpers) resolves images like prod's resolver
cached first,
then 4s/8s-retried pulls across ECR → GHCR → Docker Hub -> at every
raw-run site, timeout-bounded, daemon-aware, memoized.
Verified both ways: rate-limited registries reproduce the CI failure
verbatim unfixed, pass via the Hub fallback fixed....
## ref:
<details>
<summary>fixes: (ss)</summary>

<img width="1736" height="1296" alt="image"
src="https://github.com/user-attachments/assets/6361bebf-b1b1-4e2d-b976-af9204fff080"
/>

</details>
…ess (CLI-1988) (#6008)

## What kind of change does this PR introduce?

Go-parity fix for `supabase gen types` flag validation, implementing the
CLI-1988 parity ruling.

Fixes CLI-1988

## ⚖ Parity ruling applied

This issue was decision-gated; the ruling (Colum, 2026-07-30) was to
take the issue's recommended option: **bless the deliberate pg-meta
permissiveness, restore every undocumented Go guard byte-exactly**.

### Blessed deviation (kept, sanctioned — do not "fix" back)

Non-TypeScript `--lang` (`go`/`swift`/`python`) with `--linked`,
`--project-id`, or the implicit linked ref runs **pg-meta locally**
against the project database (project probe → preview-branch fallback →
temporary login-role credentials). The Go CLI instead hard-errors with
`Unable to generate <lang> types for selected project. Try using
--db-url flag instead.`
(`apps/cli-go/internal/gen/types/types.go:44-46`) and never runs pg-meta
for a project ref.

This permissiveness is intentional: it was already recorded in
`gen/types/SIDE_EFFECTS.md` and it resolves the user-filed CLI-1623
complaint. This PR strengthens the SIDE_EFFECTS.md wording to explicitly
mark it as a **sanctioned intentional divergence (CLI-1988)** and
records it in `docs/go-cli-porting-status.md`.

### Restored Go guards (byte-exact, verified against the compiled Go
binary)

These were TS divergences **not** documented as intentional, now
restored to Go's exact strings, ordering, and exit code 1:

- **PreRunE gate** (`cmd/gen.go:80-82`): `--postgrest-v9-compat` without
`--db-url` → `--postgrest-v9-compat must used together with --db-url`
(Go's "must used" typo preserved). Previously TS allowed `--local
--postgrest-v9-compat` and emitted a TS-only message for ref paths.
- **All four cobra mutually-exclusive flag groups**
(`cmd/gen.go:153-162`) — TS previously reproduced only the first:
  - `local` / `linked` / `project-id` / `db-url`
  - `linked` / `project-id` / `postgrest-v9-compat`
  - `linked` / `project-id` / `query-timeout`
  - `linked` / `project-id` / `swift-access-control`

Errors use cobra's exact format (`if any flags in the group [...] are
set none of the others can be; [...] were all set`, set-flags
alphabetically sorted) via the existing
`shared/cli/cobra-flag-groups.ts` helpers, and the groups are validated
in cobra's lexicographically-sorted group-key order, so multi-violation
invocations report the same group as Go (e.g. `--db-url X
--postgrest-v9-compat --project-id Y` reports the postgrest group).
- **Guard ordering matches cobra's pipeline**: flag parse (invalid
`--query-timeout` duration) → PreRunE (postgrest gate,
positional-language guard) → mutex groups. E.g. `--local --linked
--postgrest-v9-compat` now yields the PreRunE error, as Go does.
- **Mutex membership mirrors pflag `Changed`**: an explicitly negated
boolean (`--linked=false --project-id X`) still trips the group,
matching cobra.
- **Removed TS-only messages absent from Go**: `--swift-access-control
can only be used with --lang swift`, `--postgrest-v9-compat can only be
used with pg-meta type generation`, `--query-timeout can only be used
with pg-meta type generation`, and the `Warning: --query-timeout is
ignored for remote TypeScript type generation.` stderr warning. Go also
allows `--swift-access-control` with any `--lang` on
`--local`/`--db-url` (the value is always forwarded to pg-meta), which
TS now does too. `gen types --query-timeout 20s` on the implicit linked
TypeScript path now silently ignores the flag, as Go does.

### Keep-vs-restore interaction (documented, not a conflict)

Restoring the `linked`/`project-id` mutex groups means the pg-meta
tuning knobs (`--swift-access-control`, `--postgrest-v9-compat`,
`--query-timeout`) **cannot be combined with the blessed project-ref
pg-meta path** — that path always runs with pg-meta defaults (`internal`
access control, one-to-one detection on, 15s timeout). This does not
break the blessed permissiveness itself (`--linked --lang go` etc. still
reach pg-meta); it constrains only the add-on knobs, exactly as Go's
flag surface does, and `--db-url` remains the escape hatch Go's own
error message recommends. No Go guard had to be left unrestored: none of
them exists solely to enforce "never pg-meta on refs".

One known residual precedence nuance: Go resolves the linked DB config
in the root `PersistentPreRunE` *before* flag validation, so in Go an
unlinked workdir or unreachable network can surface a resolution error
(e.g. `Cannot find project ref…`, `IPv6 is not supported…`) *before* a
mutex error. The TS handler validates flags before any resolution
(consistent with all prior TS mutex ports — sso, functions, db dump), so
in those degraded environments TS reports the mutex error instead. The
guard strings themselves are byte-identical.

### Note on CLI-1623

CLI-1623 is stale either way: the complaint it tracks (non-TypeScript
typegen unusable for hosted projects) is resolved by the blessed
permissiveness that this PR pins with regression tests, so the issue no
longer reflects current behavior regardless of this ruling.

## What is the new behavior?

Previously-working TS-only combos now error with Go's exact text
(`--local --postgrest-v9-compat`, `--linked --lang swift
--swift-access-control public`, `--linked --query-timeout 30s`, …), the
TS-only friendlier gate messages are gone, and the blessed
`--linked/--project-id --lang go|swift|python` pg-meta path is unchanged
and covered by regression tests (11 guard tests verified to fail against
the previous handler, plus pins for the permissive path).
…n (CLI-1975) (#6002)

## ⚖ Parity ruling applied

This issue was **decision-gated**. Colum ruled (2026-07-30) to take the
issue's recommended option: **remap the `-o toml` / `-o yaml` encoders
to Go field-name casing and structure — full Go parity**. The
alternative (declaring the TS snake_case casing intentional) was
**rejected**. The Go CLI (`apps/cli-go/`) is the byte-parity oracle: for
every affected command the machine output now matches what the Go binary
prints, including field-name casing, ordering, zero-value inclusion,
datetime rendering, and null rendering.

**User-visible change:** scripts consuming `-o toml` / `-o yaml` from
the affected commands will now see Go-shaped keys (`ProjectRef` /
`[[branches]]`-style PascalCase in TOML, `projectref`-style lowercased
names in YAML) instead of the snake_case keys the TS CLI emitted until
now — i.e. scripts written against the long-lived Go CLI work again. `-o
json` values additionally carry Go's default HTML escaping (`<` `>` `&`
as `\u003c`-style escapes); any JSON parser decodes these transparently.

Fixes CLI-1975

## What changed

Go's `utils.EncodeOutput` hands raw Go structs to BurntSushi TOML and
yaml.v3, and neither library reads `json:` tags — keys come from the Go
**struct field names**. The TS legacy shell was instead re-encoding the
decoded snake_case JSON. This PR closes that gap for every
struct-payload command.

### New shared encoder
(`legacy/shared/legacy-go-struct-output.encoders.ts`)

A pure, spec-driven pair of encoders that reproduce the Go binary
byte-for-byte, driven by per-payload-family **Go struct specs** (field
order = Go declaration order, mirrored from
`apps/cli-go/pkg/api/types.gen.go` and command-local structs):

- **TOML (BurntSushi v1.6.0 semantics):** PascalCase field names,
primitives before sub-tables, 2-space nested table indentation, blank
line before top-level tables and every `[[array-of-tables]]` element,
nil pointers/slices/maps omitted, empty decoded arrays as `key = []`,
`time.Time` as native RFC3339Nano datetimes, Go float `'g'` formatting
with the TOML `.0` rule, BurntSushi's exact string-escape table, and
Go's runtime failure for populated `nullable.Nullable` fields.
- **YAML (yaml.v3 v3.0.1 semantics):** lowercased-whole-field-name keys,
explicit `null` for nil pointers, `[]`/`{}` for nil slices/maps,
yaml.v3's 4-column indentation algorithm (+2 inside sequence items), its
scalar-quoting resolver (old-bools, base-0 ints, floats, base-60,
timestamps → double-quoted; emitter-plain-disallowed → single-quoted),
block literals with chomping/indentation indicators, `map[bool]T`
rendering for oapi `nullable` fields, yaml.v3's natural map-key sort,
and Go `strconv.FormatFloat(_, 'g', -1, bits)` float formatting incl.
float32 rounding.

Every golden byte string in the unit tests was captured empirically from
a scratch Go program running the repo's own `utils.EncodeOutput` with
the exact library versions pinned in `apps/cli-go/go.mod`.

### Commands rewired to the spec encoders

- `branches list/create/update` (shared `branches.go-payload.ts`)
- `orgs list/create` (`orgs.go-payload.ts`)
- `projects list` (Go's `linkedProject` embed: inlined
`V1ProjectWithDatabaseResponse` fields first, `Linked` last), `projects
create` (shared create core), `projects api-keys` (`-o yaml` only — Go's
`-o toml|env` encode the `SUPABASE_*_KEY` env map, unchanged)
- `secrets list`, `sso list/show/add/update/remove`
(`sso.go-payload.ts`), `domains get/create/activate/reverify`,
`ssl-enforcement get/update` (`ssl-enforcement.go-payload.ts`),
`functions list` (replaces its bespoke per-command key maps), `backups
list`, `snippets list`, `services`, `vanity-subdomains
get/activate/check-availability` (YAML was snake_case; TOML gains Go's
declaration order)

Nil-vs-empty slice semantics follow each Go command: append-built lists
(`branches list`, `projects list`) emit nothing for `-o toml` when empty
(Go nil slice), decoded lists emit `key = []`.

### Bug-for-bug parity notes

- `snippets list -o toml` now **fails** with Go's exact error (`failed
to output toml: toml: cannot encode a map with non-string key type`)
whenever a snippet carries a `description` — BurntSushi cannot encode
`nullable.Nullable[string]` (`map[bool]string`), and the Management API
always sends the key. Go fails identically.
- `projects api-keys -o yaml` renders nullable fields as yaml.v3 renders
`map[bool]T`: `apikey:` + indented `true: <value>` when set, `{}` when
absent, `false: ""` for explicit JSON null.

### JSON HTML escaping

`encodeGoJson` (`-o json`) and `encodeGoStructJsonBody` (raw-HTTP
request bodies for `sso add/update`) now produce Go's `encoding/json`
default escaping: `<` `>` `&` → `\u003c` `\u003e` `\u0026`,
`\u0008`/`\u000c` for backspace/form feed, and escaped U+2028/U+2029 —
materially visible in `sso … metadata_xml`. Both now route through the
shared Go-faithful JSON walker (`legacy-go-json.ts`, which gains a
compact mode); `functions list`'s bespoke post-escaper was deleted. The
cli-e2e replay server compares parsed bodies, so recorded fixtures are
unaffected.

### Tests & docs

- New unit suite for the encoders with Go-captured golden bytes (quoting
matrix, block literals, floats incl. `-0`, nullable shapes, nil/empty
slices, natural key sort, hostnames nesting).
- Integration tests upgraded to byte-exact assertions for branches list
(toml+yaml, incl. a zero-value branch and the empty-list `-o toml`
no-output case), sso show (json escape + yaml + toml), backups list
(incl. `[[Backups]]`), snippets (both the Go failure and the
description-absent success bytes), plus casing fixes across
orgs/projects/secrets/services/ssl-enforcement/domains/vanity tests. The
previously-wrong `branches list` toml assertion (`name = "feat-1"`) is
now the full Go-golden document.
- An explicit exempt-proof test: `branches get -o toml` (map payload)
keeps its env-map keys verbatim — the struct remap must not apply to map
payloads (`sso info`, `status`, `postgres-config`, `network-bans`,
`branches get` are unchanged).
- SIDE_EFFECTS.md parity claims updated (domains' "intentional
snake_case divergence" note deleted; backups/secrets/sso output-shape
descriptions now byte-accurate).

## Deliberately out of scope (pre-existing divergences, noted for the
record)

- `network-restrictions get/update`: Go never encodes `-o` output for
these commands (it always prints three fixed `Printf` lines), so there
is no Go byte oracle; the TS handlers' existing `-o json|yaml|toml|env`
support is left untouched. Follow-up candidate.
- `branches list -o json` with zero branches: TS emits `[]`, Go emits
`null` (append-built nil slice). JSON values were out of CLI-1975's
scope (HTML escaping only).
- `projects list -o json`: TS sorts keys alphabetically so `linked`
sorts mid-object; Go emits it last. Same out-of-scope reasoning.
- `domains` `data.errors`/`data.messages` are modeled as raw JSON
values; Go's generated element type marshals as an empty struct —
unobservable because these arrays are empty on every reachable path
(both sides emit `[]`).
…lag builder (CLI-2005) (#6010)

## What changed

Follow-up to CLI-1983 (#5975), from kanadgupta's review: the seven
remaining hand-rolled pflag `StringSliceVar` call sites still mapped
malformed-CSV failures to a bare `err.message`, so their stderr missed
pflag's `invalid argument %q for %q flag: ...` framing. All of them now
route through the shared `legacyStringSliceFlag` builder
(`src/legacy/shared/legacy-string-slice-flag.ts`):

- `sso add --domains`
- `sso update --domains` / `--add-domains` / `--remove-domains`
- `postgres-config update --config`
- `postgres-config delete --config`
- `start --exclude` / `-x`
- `status --override-name` / `--exclude`

The builder gains an optional `{ alias }` parameter because `start
--exclude` is the one site whose Go counterpart is a `StringSliceVarP`
**with a shorthand** (`cmd/start.go:58`): pflag frames such diagnostics
with both spellings — `invalid argument %q for "-x, --exclude" flag:
...` (pflag v1.0.10 `errors.go:108-117` branches on `flag.Shorthand`) —
regardless of which spelling the user typed, so the alias has to be
registered inside the builder for the framing to come out right.

`Flag.withDefault([] as ReadonlyArray<string>)` was dropped from the
migrated flag definitions: `Flag.atLeast(0)` already yields `[]` when
the flag is unset (covered by the existing "defaults to an empty array
when unset" unit tests), and `--help` output was verified byte-identical
before/after for all six commands.

## Per-site Go parity verification

Every rendered line was verified against the Go binary built from
`apps/cli-go` (pflag v1.0.10 → `encoding/csv`). All seven sites'
malformed-CSV stderr changes user-visibly — from the bare parse-error
text to the full pflag line:

| Site | Go framing | Example (Go-verified, now byte-matched by TS) |
| --- | --- | --- |
| `sso add --domains` | `"--domains"` | `invalid argument "a\"b" for
"--domains" flag: parse error on line 1, column 2: bare " in
non-quoted-field` |
| `sso update --domains` | `"--domains"` | same as above |
| `sso update --add-domains` | `"--add-domains"` | `invalid argument
"\"x" for "--add-domains" flag: parse error on line 1, column 3:
extraneous or missing " in quoted-field` |
| `sso update --remove-domains` | `"--remove-domains"` | same shape as
`--add-domains` |
| `postgres-config update --config` | `"--config"` | `invalid argument
"a\"b" for "--config" flag: parse error on line 1, column 2: bare " in
non-quoted-field` |
| `postgres-config delete --config` | `"--config"` | `invalid argument
"\"max_connections" for "--config" flag: parse error on line 1, column
17: extraneous or missing " in quoted-field` |
| `start --exclude` / `-x` | **`"-x, --exclude"`** | `invalid argument
"a\"b" for "-x, --exclude" flag: parse error on line 1, column 2: bare "
in non-quoted-field` |
| `status --override-name` | `"--override-name"` | `invalid argument
"\"api.url=FOO" for "--override-name" flag: parse error on line 1,
column 13: extraneous or missing " in quoted-field` |
| `status --exclude` | `"--exclude"` | `invalid argument "a\"b" for
"--exclude" flag: parse error on line 1, column 2: bare " in
non-quoted-field` |

For `postgres-config`, the parse error also correctly precedes the
`--experimental` gate (cobra parses flags before `PersistentPreRunE`),
asserted in the experimental-gate integration suite.

## Multiline / blank-line semantics findings

CLI-1983's parser rewrite changed `legacyParseStringSliceFlag` itself,
so all seven sibling sites silently inherited the first-record-only /
EOF-on-blank semantics. I verified each site against the Go binary:

- **First-record-only**: `--<flag> $'a\nb"c'` raises **no** parse error
in Go at any of the seven sites (pflag calls `csv.Reader.Read()` once;
the malformed second line is silently dropped). Observable proof for
`start`: `start -x $'a\nb"c'` warns `The following container names are
not valid to exclude: a` — only the first record survives. TS matches.
- **Blank-only → EOF**: `--<flag> $'\n'` fails in Go with `invalid
argument "\n" for "--<flag>" flag: EOF` at every site (with the `-x,
--exclude` framing on `start`). TS matches.
- **No sibling site's existing tests asserted stale pre-rewrite
behaviour** — they simply had no multiline/blank-only coverage at all,
and their malformed-CSV tests only asserted `Exit.isFailure` without the
message. This PR adds exact-message assertions per flag plus
first-record-only and blank-only-EOF vectors per site.

## Test coverage added

- Per-site unit tests: exact pflag-framed diagnostics (including the
shorthand framing for `start`), first-record-only multiline vectors,
blank-only EOF vectors.
- Per-family integration tests running the whole command tree
(`Command.runWith`) and asserting the exact rendered message via
`normalizeCause`, mirroring the network-bans/network-restrictions prior
art from CLI-1983: new `sso.string-slice-flags.integration.test.ts`,
`start.string-slice-flags.integration.test.ts`,
`status.string-slice-flags.integration.test.ts`, plus malformed-CSV
cases in the existing
`postgres-config.experimental-gate.integration.test.ts`.
- `start`'s `--exclude` flag is hoisted to an exported
`legacyStartExcludeFlag` (mirroring `status`/`sso` conventions) so it is
unit-testable.
- SIDE_EFFECTS.md for all six commands gains the parse-time failure
exit-code row (mirroring CLI-1983's doc updates).
- Stale comments referencing the deleted `csvStringSliceFlag` helper in
`legacy-db-target-flags.ts`/`.unit.test.ts` were updated; all
helper-built flag names remain hand-registered in
`VALUE_CONSUMING_LONG_FLAGS`, so telemetry argv parsing is unaffected.

## Overlap note: PR #5974

Open PR #5974 (`columferry/cli-1982-...`) touches sso command files
(`sso.pflag-reconcile.ts`, add/update handlers). This PR's sso changes
are deliberately minimal — the flag definition blocks in
`add.command.ts`/`update.command.ts`, their unit tests, one new
family-level integration test file, and one SIDE_EFFECTS.md row. Whoever
merges second should re-verify the sso flag definitions still route
through `legacyStringSliceFlag` after conflict resolution.

## Review notes (deliberately left open)

- The `--schema` slice-flag family (`gen types`, `db
lint/dump/pull/diff`, `db schema declarative generate`) still uses the
hand-rolled `Flag.mapTryCatch(legacyParseSchemaFlags, err =>
err.message)` pattern via `legacy-schema-flags.ts`. It is not in
CLI-2005's scope (and several of those are `StringSliceVarP` with `-s`
shorthands needing their own per-site Go verification) — candidate for a
follow-up issue.
- The pathological double-error case (`-o bad` plus malformed CSV in one
invocation): TS surfaces the CSV parse error while Go's winner depends
on argv order; both exit non-zero. Same accepted approximation as
CLI-1983, already documented on the network-bans/network-restrictions
flag comments.
- `cli-go:lint:check` fails with 5 pre-existing gosec findings unrelated
to this change (no Go files touched).

Fixes CLI-2005
…er (CLI-1982) (#6040)

## What kind of change does this PR introduce?

Refactor — pure move + rename, zero behavior change.

## What is the current behavior?

`sso.pflag-reconcile.ts` and `sso.load-profile.ts` (added in #5974 to
reconcile pflag/viper-vs-Effect-parser divergence for
`--profile`/`--workdir`/bool/enum flags, and to emulate Go's
`LoadProfile`) live under the sso-specific command directory and are
coupled to two sso-specific error types (`LegacySsoWorkdirError`,
`LegacySsoProfileError`), even though every function in them is already
generic. This was flagged in [#5974's
review](#5974 (comment)):
the logic doesn't scale to future command families that need the same
pflag-vs-Effect-parser reconciliation.

## What is the new behavior?

- Moved `sso.pflag-reconcile.ts` →
`apps/cli/src/legacy/shared/legacy-pflag-reconcile.ts`, and
`sso.load-profile.ts` →
`apps/cli/src/legacy/shared/legacy-profile-load.ts` (plus their unit
tests).
- Renamed every export to drop the `Sso` infix (e.g.
`legacySsoResolvePflagProfile` → `legacyResolvePflagProfile`).
- Replaced `LegacySsoWorkdirError`/`LegacySsoProfileError` with new
generic `LegacyPflagWorkdirError`/`LegacyProfileLoadError`, deleting the
two sso-specific classes from `sso.errors.ts` with no compatibility
shims.
- Updated `sso add`/`sso update` handlers and their tests/SIDE_EFFECTS
docs to the new location.

All doc comments documenting binary-verified Go-parity behavior (across
many #5974 review rounds) are preserved verbatim. This is a
follow-through on a review suggestion that was originally deferred to a
follow-up ticket — implementing it directly instead.
@supabase-cli-releaser
supabase-cli-releaser Bot requested a review from a team as a code owner August 4, 2026 03:10
@supabase-cli-releaser supabase-cli-releaser Bot added the do not merge Approve to apply; do not merge. label Aug 4, 2026
// The merge seeds from the reconciled host's GET response.
const domains = (put?.body as { domains?: string[] })?.domains ?? [];
expect([...domains].sort()).toEqual(["old1.com", "old2.com"]);
expect(api.requests.some((r) => r.url.startsWith("http://first.example"))).toBe(false);
const entitlements = api.requests.find((r) => r.url.includes("/entitlements"));
expect(project?.url).toBe(`http://second.example/v1/projects/${LEGACY_VALID_REF}`);
expect(entitlements?.url).toBe("http://second.example/v1/organizations/acme/entitlements");
expect(api.requests.some((r) => r.url.startsWith("http://first.example"))).toBe(false);

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97d6a66ecd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// in place and only ever reclaimed later, in the publish step
// below, once a fully-staged replacement is ready to atomically
// take its place.
const isComplete = yield* fs.exists(path.join(cacheDir, CACHE_COMPLETE_MARKER));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to markerless cache when downloads fail

When a user already has a cache entry written by any earlier CLI release, that directory is non-empty but lacks the new .supabase-cache-complete marker, so this check skips it and immediately enters the download path. If the user is offline or GitHub is unavailable, resolveWithMetadata now fails even though the previously working binary is still present on disk; keep markerless legacy entries as a fallback when replacement cannot be downloaded/published.

Useful? React with 👍 / 👎.

* conflict deliberately does not match, so a real spec conflict still fails.
*/
function legacyIsVolumeAlreadyExistsError(stderr: string): boolean {
return /volume (?:with name \S+ )?already exists/iu.test(stderr);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not swallow conflicting existing volumes

When volume create returns the spec-conflict message this helper explicitly calls out (volume with name <name> already exists ... but was not created for the current specification), the leading “already exists” text still satisfies this regex. legacyEnsureStartVolume then treats that non-zero exit as success and continues start against a volume whose requested options/labels were rejected; tighten the match so only the idempotent already-exists case is swallowed.

AGENTS.md reference: apps/cli/AGENTS.md:L247-L257

Useful? React with 👍 / 👎.

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.

3 participants