Skip to content

feat(cli): port supabase start command to native TypeScript#5847

Merged
Coly010 merged 94 commits into
developfrom
columferry/cli-1323-port-supabase-start
Jul 24, 2026
Merged

feat(cli): port supabase start command to native TypeScript#5847
Coly010 merged 94 commits into
developfrom
columferry/cli-1323-port-supabase-start

Conversation

@Coly010

@Coly010 Coly010 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What changed

Ports supabase start from a Go-binary proxy to native TypeScript in the legacy CLI shell (CLI-1323), continuing the local-dev-stack shell migration alongside the already-ported stop/status/db push/db reset/db start.

Talks directly to Docker/Podman via subprocess, mirroring Go's sequential per-container DockerStart — no Docker Compose, and no @supabase/stack/effect orchestration (that runtime targets a deliberately different local-dev product and would silently manage the wrong set of containers). Brings up all 14 containers in Go's real start order, with per-service config-boolean + --exclude gating, Go-byte-exact env/image resolution, a bulk health-check phase (Docker healthcheck for most services, an HTTP-HEAD-through-Kong bypass for PostgREST/Edge Runtime), and full rollback on any bring-up failure.

Also natively implements the fresh-volume DB schema/migration/seed pipeline, Edge Runtime container bring-up, and fresh-volume storage bucket seeding — previously tracked as out-of-scope follow-ups for this port, now closed. Only the linked-project version-check suggestion (a best-effort "update available" hint with zero Management API dependency otherwise) remains out of scope by design.

Why

Continues the legacy-shell migration from Go-binary-proxy commands to native TypeScript, closing CLI-1323.

Reviewer-relevant context

  • legacyParseGoDuration (the config.toml duration-string parser feeding Go-parity env vars like GOTRUE_SESSIONS_TIMEBOX) silently accepted a malformed duration with no digits (a bare unit like "s", or a lone "." with no digits on either side) and returned 0 instead of erroring like Go's real time.ParseDuration — both cases now throw Go's exact time: invalid duration "..." message.
  • start.services.ts's descriptive enabledGate metadata for Mailpit referenced the deprecated inbucket config section instead of its local_smtp rename. Fixed, and added a mechanical cross-check test that evaluates every service's enabledGate string against start.gates.ts's real computed gate across a battery of synthetic configs, so a future drift between the two fails loudly instead of silently.
  • Confirmed and closed the PostgREST health-check's TLS/CA trust gap for [api.tls] enabled = true local stacks (self-signed Kong cert) — the local-Kong-CA-trust mechanism already used by seed buckets/storage/db reset is now wired into start's health-check HTTP client too.

Note

This branch is currently 9 commits behind develop (unrelated deps/docker bump commits) — opening as draft to get CI/review visibility; happy to rebase before this comes out of draft.

Ports `supabase start` from a Go-binary proxy to native TypeScript in the
legacy CLI shell (CLI-1323), continuing the local-dev-stack shell migration
alongside the already-ported `stop`/`status`/`db push`/`db reset`/`db start`.

## What changed

Talks directly to Docker/Podman via subprocess, mirroring Go's sequential
per-container `DockerStart` — no Docker Compose, and no
`@supabase/stack/effect` orchestration (that runtime targets a deliberately
different local-dev product and would silently manage the wrong set of
containers). Brings up all 14 containers in Go's real start order, with
per-service config-boolean + `--exclude` gating, Go-byte-exact env/image
resolution, a bulk health-check phase (Docker healthcheck for most services,
an HTTP-HEAD-through-Kong bypass for PostgREST/Edge Runtime), and full
rollback on any bring-up failure.

Also natively implements the fresh-volume DB schema/migration/seed pipeline,
Edge Runtime container bring-up, and fresh-volume storage bucket seeding —
previously tracked as out-of-scope follow-ups for this port, now closed.
Only the linked-project version-check suggestion (a best-effort "update
available" hint with zero Management API dependency otherwise) remains out
of scope by design.

## Notable review-driven fixes

- `legacyParseGoDuration` (the `config.toml` duration-string parser feeding
  Go-parity env vars like `GOTRUE_SESSIONS_TIMEBOX`) silently accepted a
  malformed duration with no digits (a bare unit like `"s"`, or a lone `"."`
  with no digits on either side) and returned `0` instead of erroring like
  Go's real `time.ParseDuration` — both cases now throw Go's exact
  `time: invalid duration "..."` message.
- `start.services.ts`'s descriptive `enabledGate` metadata for Mailpit
  referenced the deprecated `inbucket` config section instead of its
  `local_smtp` rename. Fixed, and added a mechanical cross-check test that
  evaluates every service's `enabledGate` string against `start.gates.ts`'s
  real computed gate across a battery of synthetic configs, so a future
  drift between the two fails loudly instead of silently.
- Confirmed and closed the PostgREST health-check's TLS/CA trust gap for
  `[api.tls] enabled = true` local stacks (self-signed Kong cert) — the
  local-Kong-CA-trust mechanism already used by `seed buckets`/`storage`/
  `db reset` is now wired into `start`'s health-check HTTP client too.

Closes CLI-1323
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically.

Please open an issue first, wait for a maintainer to add the open-for-contribution label, then open a pull request that links the issue with a closing keyword (e.g. Closes #123).
See CONTRIBUTING.md for the full workflow. Once a maintainer adds the open-for-contribution label to a linked open issue, reopen or open a new pull request and it will be accepted.

@github-actions github-actions Bot closed this Jul 9, 2026
@Coly010 Coly010 reopened this Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically.

Please open an issue first, wait for a maintainer to add the open-for-contribution label, then open a pull request that links the issue with a closing keyword (e.g. Closes #123).
See CONTRIBUTING.md for the full workflow. Once a maintainer adds the open-for-contribution label to a linked open issue, reopen or open a new pull request and it will be accepted.

@github-actions github-actions Bot closed this Jul 9, 2026
@Coly010 Coly010 self-assigned this Jul 9, 2026
@Coly010 Coly010 reopened this Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically.

Please open an issue first, wait for a maintainer to add the open-for-contribution label, then open a pull request that links the issue with a closing keyword (e.g. Closes #123).
See CONTRIBUTING.md for the full workflow. Once a maintainer adds the open-for-contribution label to a linked open issue, reopen or open a new pull request and it will be accepted.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

👋 Thanks for the contribution! This pull request isn't linked to a tracked issue, so it's being closed automatically.

Please open an issue first, wait for a maintainer to add the open-for-contribution label, then open a pull request that links the issue with a closing keyword (e.g. Closes #123).
See CONTRIBUTING.md for the full workflow. Once a maintainer adds the open-for-contribution label to a linked open issue, reopen or open a new pull request and it will be accepted.

@github-actions github-actions Bot closed this Jul 9, 2026
@avallete avallete reopened this Jul 9, 2026
pull Bot pushed a commit to chizee/cli that referenced this pull request Jul 9, 2026
)

The contribution gate identified internal maintainers solely from the
PR's `author_association`, which GitHub only reports as `MEMBER` when a
user's organization membership is public. A private org member (e.g. a
`supabase/cli` team member who keeps membership private) is reported as
`CONTRIBUTOR`/`NONE`, so the gate wrongly closed their PRs as
"no-linked-issue" (see supabase#5847).

Resolve maintainer status from the author's effective repository
permission (`admin`/`write`), which reflects team/org-granted access
that `author_association` does not surface, falling back to it only when
the cheap signals (bot, public internal association) are inconclusive.
The permission endpoint needs just `Metadata: read`, already covered by
the workflow's `contents: read`.


Claude-Session: https://claude.ai/code/session_01D41gYiFBSU7adE4ppnUUKg

---------

Co-authored-by: Claude <noreply@anthropic.com>
@Coly010

Coly010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 623dd8b18e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.gates.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/services/postgres.service.ts
@Coly010

Coly010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 623dd8b18e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/lib/container-lifecycle.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/lib/db-setup.ts
Comment thread apps/cli/src/legacy/commands/start/start.gates.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/services/gotrue.service.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
supabase start's TS port had five spots where Go's real behaviour was
silently dropped:

- supabase/.temp/storage-migration (the linked project's Storage
  migration pin) was never read, so DB_MIGRATIONS_FREEZE_AT was always
  empty on a fresh DB setup and the Storage container itself.
- supabase/.temp/{gotrue,rest,storage,realtime,studio,pgmeta,logflare,
  pooler}-version pins were never applied to the images start
  pulls/creates, unlike Go's Config.Load rewrite. Hoisted the existing
  services command's reader into shared/legacy-service-version-overrides.ts
  and reused services.shared.ts's tag-rewrite helpers instead of a third
  copy.
- --network-id was read by several other native ports but never by
  start itself, so the override never reached any container or the
  Docker network start creates.
- SUPABASE_API_PORT's env override was computed for status URLs but
  never exposed for Kong/Edge Runtime's own container specs, so they
  kept publishing/binding the un-overridden config.api.port.
- Studio's function bind mounts were hardcoded to [], unlike Go's
  unconditional (Edge-Runtime-independent) PopulatePerFunctionConfigs
  call — extracted the existing per-function bind computation in
  shared/functions/serve.ts into a reusable resolveFunctionBindMounts
  so both callers share one calculation.
@Coly010

Coly010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

…l in start

Three more Go-parity gaps in supabase start found by review:

- db.root_key (including encrypted: values) was never resolved or
  passed to the Postgres container, so every project booted with the
  hard-coded default pgsodium key instead of a customized one, breaking
  decryption of existing encrypted data. Resolved in
  legacyResolveLocalConfigValues off the raw config document (unmodeled
  in @supabase/config's schema), reusing the existing decrypt helpers.
- DockerStart's Linux-only host.docker.internal:host-gateway mapping
  was already correctly ported for the one-shot migrate jobs and Edge
  Runtime bring-up, but never made it into the common
  legacyStartContainer path the other 13 services go through.
- auth.external_url (also unmodeled in the schema, with its own Go
  regression test) was never read, so GoTrue's API_EXTERNAL_URL/JWT
  issuer default/mailer verify URL/OAuth redirect fallbacks always
  derived from apiUrl even when a project intentionally exposed auth at
  a different host.
@Coly010

Coly010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

…es tag ordering

Three more Go-parity gaps found by review:

- [auth.email.smtp] present without an explicit enabled key should
  default to enabled (Go sets this at load time), but the schema always
  decodes enabled: false when the key is absent, so GoTrue silently
  fell back to Mailpit. Reuses the existing correct resolution
  already implemented for config validation, exposed as
  legacyResolveAuthEmailSmtp.
- auth.external providers outside the schema's fixed ~19-provider set
  (e.g. a custom [auth.external.my_oidc] block) never reached GoTrue's
  env, even though Go's Auth.External is a genuine map iterated
  unconditionally and this port's own config validation already
  accepts arbitrary provider names. Reuses the same raw-document
  iteration validateAuthExternalProviders already established.
- A registry override with a port (SUPABASE_INTERNAL_IMAGE_REGISTRY=
  localhost:5000) broke the Postgres version-tag comparison, since Go
  compares the pre-registry-rewrite image while this port was comparing
  the already-rewritten one. Threaded the original image through as a
  separate configImage field used only for that comparison.
@Coly010

Coly010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Coly010 added 2 commits July 22, 2026 12:53
…agerly

Go's Config.Load decodes storage.analytics.{max_namespaces,max_tables,
max_catalogs} and storage.vector.{max_buckets,max_indexes} as plain
uints in the same unconditional Viper/mapstructure pass as the
storage.vector.enabled/storage.s3_protocol.enabled fields already
validated eagerly here (pkg/config/storage.go:28-39), before start
touches Docker. The native port never validated these five env
overrides at all, so a malformed value was silently ignored instead
of hard-failing before any Docker work, regardless of --exclude storage.

review: PRRT_kwDOErm0O86S5NR3
Go folds a SUPABASE_AUTH_EMAIL_TEMPLATE_<NAME>_CONTENT (or
notification _CONTENT) env override into Content *string via the same
generic Viper AutomaticEnv bind as every other field (config.go:749),
before Config.Validate runs. (e *email) validate (config.go:1293-1301)
then rejects the template if Content is set without ContentPath.

legacyResolveAuthEmail only applied _SUBJECT/_CONTENT_PATH overrides,
and readAuthEmailTemplateContent's presence check only looked at the
raw TOML content key, so a _CONTENT override was invisible on both
sides: native start could accept and launch Docker on a config Go's
CLI would refuse. Fold the env override into content_present, computed
once in legacyResolveAuthEmail and consumed by the validator instead of
re-derived from the raw document.

review: PRRT_kwDOErm0O86S5NR7

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One minor nitpick.

Comment thread apps/cli/src/legacy/shared/legacy-docker-image-resolve.ts Outdated
Coly010 added 2 commits July 23, 2026 17:31
… pull retry

Go's DockerResolveImageIfNotCached (apps/cli-go/internal/utils/docker.go:
326-334) inspects via the typed Engine API client and distinguishes
errdefs.IsNotFound from any other inspect error, returning the latter
immediately without ever entering the pull-retry loop.

hasLocalImage shelled out to `docker image inspect` with stdout/stderr
both ignored, so a down daemon and a genuine cache miss looked
identical: both non-zero exits. A down daemon fell through the full
3-registry x 3-attempt pull-retry loop (~36s of backoff) before a
generic "failed to pull from all registries" message instead of
failing fast with a clear daemon error.

Capture stderr (stdout stays ignored to avoid the cache-hit
pipe-buffer-deadlock hazard) and check it against
legacyIsDockerDaemonUnreachable, failing immediately on a match. A
genuine spawn failure (no docker/podman binary) now also propagates
instead of being silently swallowed to false, matching how the pull
loop already treats that case.

review: PRRC 3639121719 (#5847 (comment))

@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: db815ffcac

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/start/lib/container-lifecycle.ts
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts Outdated
Coly010 added 2 commits July 23, 2026 18:33
…pt test

"rolls back on a SIGINT-style interruption mid-bring-up" only waited
for a `create` call before interrupting, but
mockStartContainerCliSpawner resolves every Docker call synchronously
with no fake delay. Effect.forkChild({ startImmediately: true }) runs
a forked fiber synchronously up to its first genuine suspension point,
so a whole bring-up (10+ containers, no real waits) could occasionally
run to full completion before the test's own polling loop was ever
scheduled, making Fiber.interrupt a no-op on an already-succeeded
fiber. Failed intermittently in CI (run 30027017791) though never
locally.

Marks the db container never-healthy, mirroring the neighboring
post-bring-up test, so bringUp's own Postgres health-check wait
genuinely retries on its real 1s Schedule.spaced backoff before the
fiber is interrupted -- a deterministic suspension point instead of a
race against a zero-delay mock. Ran the isolated test 5x locally
before this change to confirm the fix holds, plus the full file (142
tests) after.

ci: Run unit and integration tests
writeFile's mode option is only a creation-time hint the kernel ANDs
with the process umask, not a chmod. Under umask 077/027, the staged
secret files (bind-mounted read-only into a non-root in-container
reader -- Kong uid 100, Postgres's dropped-root entrypoint) landed at
0600 instead of the intended world-readable 0644, causing EACCES in
the container.

Go has no equivalent exposure here -- it heredocs secret content
directly into the container's entrypoint, never touching the host
filesystem -- so this is judged on the TS implementation's own merits,
not parity. Force the final mode with an explicit chmod after
writeFile.

review: PRRT_kwDOErm0O86TVHX1

@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: 2f7c84d4e9

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/start/lib/docker-create-args.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Coly010 added 2 commits July 23, 2026 19:06
…table

Go's mergeDefaultValues (apps/cli-go/pkg/config/config.go:690-698)
merges an ejected default config.toml into Viper before the user's
own file. That default template unconditionally includes uncommented
[auth.sms.twilio] (templates/config.toml:288-293) and
[auth.external.apple] (templates/config.toml:322-335) tables -- the
only SMS provider and the only external OAuth provider given a real
default section, every sibling is just named in a comment. Since
Viper merges never delete keys, those two providers' fields are
always registered with Viper, so their env overrides apply even when
the user's config.toml never declares the table -- confirmed for
twilio by running the real Go binary directly.

legacyResolveAuthSms and legacyResolveAuthExternalProviders gated
every provider on the raw document's table being present, so an
env-only Twilio or Apple setup (SUPABASE_AUTH_SMS_TWILIO_ENABLED=true
or SUPABASE_AUTH_EXTERNAL_APPLE_ENABLED=true with no corresponding
TOML table) was silently dropped instead of taking effect. The other
4 SMS providers and every other external OAuth provider have no
default template entry, so their presence gate is unchanged and still
correct parity.

review: PRRT_kwDOErm0O86TVHX3, PRRT_kwDOErm0O86TVHX7
legacyResolveLocalConfigValues's own SMS validation only runs
`if (authEnabled)`, so with auth.enabled = false that path is skipped
entirely. start.handler.ts separately calls legacyResolveAuthSms
directly and unconditionally to validate auth.sms.max_frequency, but
the call wasn't wrapped -- a malformed override (e.g.
SUPABASE_AUTH_SMS_ENABLE_SIGNUP=bad) threw a plain Error that escaped
as an unhandled Effect defect instead of the normal
LegacyStartInvalidConfigError config-load failure.

Wrap the call in Effect.try, mirroring the existing resolvedEmail fix
a few lines above for the identical bug class.

review: PRRT_kwDOErm0O86TVhiT

@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: 789d7c7077

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/legacy-go-duration.ts Outdated
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts Outdated
Coly010 added 2 commits July 23, 2026 21:05
Go's time.ParseDuration rejects any duration whose accumulated
nanosecond count exceeds math.MaxInt64 (~292.47 years, real max
2562047h47m16.854775807s), and truncates a sub-nanosecond fractional
remainder toward zero rather than rounding
(uint64(float64(f) * ...) is a truncating conversion).
legacyParseGoDuration had neither check: an extreme value like
auth.sms.max_frequency = "2562048h" silently produced an imprecise
large number instead of throwing, and "0.5ns" rounded up to "1ns"
instead of truncating to "0s".

Since native start relies on this parser for eager GoTrue/db duration
validation before Docker work, a malformed or extreme user-configured
value could be accepted or mis-normalized instead of failing like Go.
Switch Math.round to Math.trunc for the fraction and add an overflow
check against math.MaxInt64, reusing the existing
"time: invalid duration ..." error convention.

review: PRRT_kwDOErm0O86TWwAD
Every field routed through legacyEnvOverrideUint (auth.jwt_expiry,
db.major_version, db.pooler.default_pool_size,
realtime.max_header_length, api.max_rows, storage max-count fields,
...) is a plain Go uint, 64 bits wide on every platform this CLI
ships for. Go decodes these via mapstructure's decodeUint under
Viper's WeaklyTypedInput, which runs strconv.ParseUint(str, 0, 64) --
genuinely failing for any value exceeding 2^64-1. The TS helper only
checked that the override was all digits and returned Number(value),
silently accepting (and losing precision on) an overflowing value
like SUPABASE_AUTH_JWT_EXPIRY=18446744073709551616 instead of
rejecting it like Go does.

Add a BigInt-based bound check (not a Number comparison, which can't
exactly represent values this close to 2^64 and would incorrectly
reject legitimate large uint64 values Go itself accepts).

review: PRRT_kwDOErm0O86TWwAF

@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: c0f5433fb0

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/legacy-go-duration.ts Outdated
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts Outdated
Coly010 added 2 commits July 23, 2026 22:07
The previous overflow fix compared total against
Number(9223372036854775807n), which rounds UP to
9223372036854776000 -- a value 1ns past Go's true math.MaxInt64
ceiling (9223372036854775808ns, i.e. 2^63) could still pass the check.

The precision problem went deeper than the bound: the whole
accumulator (total, and each term's n/frac/post) was a plain number,
and durations near Go's real max (~9.223e18ns) are already ~1000x past
Number.MAX_SAFE_INTEGER, so the accumulator itself silently rounded
near the boundary -- independent of the bound's own precision. This
could both accept a genuinely overflowing value and reject Go's actual
valid maximum, depending on which way a given input happened to round.

Rewrite the parse loop to accumulate in BigInt throughout, converting
to Number only at the very end for the returned value (still lossy
that close to the ceiling, an accepted limit given real durations are
nowhere near it -- what matters, rejecting out-of-range overrides like
Go does, no longer depends on number precision at all).

review: PRRT_kwDOErm0O86TYJTp
Go decodes every uint-typed config field via
strconv.ParseUint(str, 0, 64), where base 0 auto-detects the numeric
base from a prefix: 0b/0B -> binary, 0o/0O -> octal, 0x/0X -> hex, and
a bare leading 0 followed by more digits is ALSO octal -- so "010"
parses as decimal 8, not 10. Underscores are permitted between digits
in base-0 mode. ParseUint never accepts a leading sign. Verified
against a real Go binary across prefix casing, underscore placement,
invalid-octal-digit rejection, and sign rejection.

legacyEnvOverrideUint only accepted /^\d+$/ and parsed decimally, so
SUPABASE_DB_POOLER_MAX_CLIENT_CONN=0100 (or similar zero-padded
values on any of this helper's callers -- api.max_rows,
auth.jwt_expiry, db.major_version, etc.) silently resolved to a
DIFFERENT number than Go's own CLI would use, not just a rejection
mismatch -- a zero-padding user could get 64 in Go and 100 in native
start.

Add parseGoBaseZeroUint, a pure helper matching this grammar exactly,
and route legacyEnvOverrideUint through it in place of the decimal-
only check, preserving the existing LEGACY_UINT_MAX overflow bound.

review: PRRT_kwDOErm0O86TYJTw

@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: 57fab71dc5

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Coly010 added 2 commits July 24, 2026 09:34
… overrides

Two related config-load gaps, both fixed together since they're
adjacent changes in the same resolver file:

legacyEnvOverridePort only accepted /^\d+$/ and parsed decimally, the
same bug already fixed for the uncapped legacyEnvOverrideUint helper.
Go decodes every SUPABASE_*_PORT override as uint16 via
strconv.ParseUint(str, 0, 16) -- base 0 auto-detects from a prefix, so
SUPABASE_LOCAL_SMTP_PORT=010 parses as octal 8 in Go, not decimal 10,
and 0x1F90 is accepted as 8080 rather than rejected. Route
legacyEnvOverridePort through the existing parseGoBaseZeroUint helper
and apply its own uint16 bound afterward, same split
strconv.ParseUint itself uses between parsing the literal and
checking the bit width.

legacyResolveLocalConfigValues read
config.experimental.webhooks?.enabled/config.experimental.pgdelta?.format_options
straight off the decoded config with no env-override applied at all,
unlike every sibling analytics.* field a few lines above. Go binds
SUPABASE_EXPERIMENTAL_WEBHOOKS_ENABLED/
SUPABASE_EXPERIMENTAL_PGDELTA_FORMAT_OPTIONS through
ExperimentalBindStruct/AutomaticEnv before experimental.validate()
runs -- the same mechanism the db/migration loader already applies
correctly for the pgdelta override. Apply the same
legacyEnvOverrideBool/legacyEnvOverride calls here.

review: PRRT_kwDOErm0O86TZRqh, PRRT_kwDOErm0O86TZRql, PRRT_kwDOErm0O86TZRqx
Two config-load gaps in start.handler.ts, fixed together since both
land in the same eager-validation region:

auth.passkey.enabled/auth.webauthn.* and per-provider
auth.external.<name>.{enabled,skip_nonce_check,email_optional} decode
in Go's same unconditional Config.Load pass as the neighboring
auth.rate_limit/auth.web3/auth.oauth_server fields already fixed
eagerly here. The TS resolvers for these (resolveGotruePasskeyWebauthn,
legacyResolveAuthExternalProviders) were only reached from
resolveGotrueEnvInput's gotrue-only branch, itself gated on
auth.enabled and --exclude gotrue -- so a malformed
[auth.passkey] enabled = "bad" (or a custom provider's enabled flag)
with auth disabled or gotrue excluded was silently accepted instead of
failing like Go's Config.Load does. Hoist eager, result-discarding
calls to both resolvers next to the existing rate_limit/web3/
oauth_server checks.

Go's DecryptSecretHookFunc decrypts every dotenvx encrypted: value in
edge_runtime.secrets unconditionally during Config.Load, so the real
Edge Runtime container always receives plaintext. The native path only
did env() interpolation/redaction via toPlainEdgeRuntimeConfig, never
decryption -- ciphertext would reach the container's env file
verbatim. Decrypt each secret value with the existing
legacyIsEncryptedSecret/legacyCollectDotenvPrivateKeys/
legacyDecryptSecret helpers (already used for db.vault) right before
building the Edge Runtime bring-up input.

review: PRRT_kwDOErm0O86TZRqo, PRRT_kwDOErm0O86TZRqt

@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

const dbInput: LegacyDbInput = { port: dbPort, majorVersion };

P2 Badge Validate ssl_enforcement env bools

When [db.ssl_enforcement] is present and SUPABASE_DB_SSL_ENFORCEMENT_ENABLED=bad overrides its enabled value, Go decodes that bool during Config.Load and rejects the config before any Docker work. Here the DB validation input only carries port and majorVersion, and neither this resolver nor legacyCheckDbToml reads the SSL-enforcement override, so native legacy start can run with an env override the Go CLI fails; resolve this bool override during config loading as well.

AGENTS.md reference: apps/cli/AGENTS.md:L246-L256

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Comment thread apps/cli/src/legacy/commands/start/start.handler.ts
Coly010 added 3 commits July 24, 2026 10:32
…s flag

Go's Config.Load decodes analytics.vector_port and storage.analytics.enabled
unconditionally in the same pass as their already-validated sibling fields
(analytics.port; storage.analytics.max_namespaces/max_tables/max_catalogs,
storage.vector.enabled, storage.s3_protocol.enabled). Native start left both
fields to be read lazily deep in the container-spec builders, so a malformed
env override was silently accepted instead of hard-failing like Go.

Resolves review threads PRRT_kwDOErm0O86TZRq1, PRRT_kwDOErm0O86Tf79s.
[db.network_restrictions] ships uncommented in Go's embedded default
config.toml and NetworkRestrictions is a non-pointer db struct field, so
Config.Load always registers a default and decodes this override
unconditionally, same bucket as db.port/db.major_version (already validated
here). Native start never validated it at all.

Resolves review thread PRRT_kwDOErm0O86Tf79o.
legacyCheckDbToml (shared by start/db push/db pull/db reset/db start) only
extracted bucket names from [storage.buckets.*]; the per-bucket
file_size_limit was parsed lazily inside legacySeedBucketsRun, reached only
on a fresh volume with Storage actually enabled. A malformed value on a
reused-volume restart, or when the stack is already running, went completely
unvalidated, unlike Go's Config.Load, which decodes every bucket's
file_size_limit via the sizeInBytes hook unconditionally.

Resolves review thread PRRT_kwDOErm0O86Tf79w.
@Coly010
Coly010 enabled auto-merge July 24, 2026 09:46
@Coly010
Coly010 added this pull request to the merge queue Jul 24, 2026
Merged via the queue into develop with commit 3368ab0 Jul 24, 2026
41 checks passed
@Coly010
Coly010 deleted the columferry/cli-1323-port-supabase-start branch July 24, 2026 09:56

@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: 41b84ede22

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -379,7 +727,10 @@ function resolveSignedKey(
const LegacyJwkSchema = Schema.Struct({
kty: Schema.String,
kid: Schema.optionalKey(Schema.String),
use: Schema.optionalKey(Schema.String),
key_ops: Schema.optionalKey(Schema.Array(Schema.String)),
alg: Schema.optionalKey(Schema.String),

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 Reject invalid signing-key algorithms

When auth.signing_keys_path contains multiple keys and a later key has alg: "HS256" or any value other than ES256/RS256, Go's JSON decode into the Algorithm type rejects the file during Config.Load. This schema accepts arbitrary strings, and only the first key is later checked while generating the implicit anon/service-role keys, so native start can proceed and pass invalid GOTRUE_JWT_KEYS/JWKS metadata to services; narrow alg here to the same two values when decoding the signing-keys file.

Useful? React with 👍 / 👎.

const ordered: Record<string, unknown> = { kty: key.kty };
if (key.kid !== undefined && key.kid.length > 0) ordered["kid"] = key.kid;
if (key.use !== undefined && key.use.length > 0) ordered["use"] = key.use;
if (key.key_ops !== undefined) ordered["key_ops"] = key.key_ops;

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 Omit empty key_ops from GoTrue keys

When a configured signing key explicitly contains "key_ops": [], Go's json:",omitempty" on the []string field omits key_ops from the GOTRUE_JWT_KEYS payload, but this serializer emits an empty array. The JWKS path already normalizes empty operations away, so GoTrue receives a different private-key document than the stable CLI and key-ops-aware parsing can treat it as a key with no permitted operations; skip key_ops when the array is empty.

Useful? React with 👍 / 👎.

Comment on lines +51 to +54
Effect.map((content) => content.trim()),
Effect.orElseSucceed(() => ""),
);
if (version.length > 0) {

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 drop whitespace-only version pins

When a linked supabase/.temp/*-version file exists but contains only whitespace, Go tests the raw file length before trimming in replaceImageTag, so the pin is still applied as an empty image tag and start fails when it tries to pull that invalid reference. This helper trims before deciding whether the pin exists, so a corrupt/interrupted linked-version file is silently ignored and native start/services fall back to defaults instead of matching the stable CLI's visible bad pin; keep presence detection separate from the trimmed tag.

AGENTS.md reference: apps/cli/AGENTS.md:L246-L256

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants