Skip to content

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

Open
Coly010 wants to merge 31 commits into
developfrom
columferry/cli-1323-port-supabase-start
Open

feat(cli): port supabase start command to native TypeScript#5847
Coly010 wants to merge 31 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

Coly010 commented Jul 11, 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.

@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: 03e943c773

ℹ️ 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
…ing's resolved config

effectiveLocalStorageConfig (reused for the health-check CA lookup and both
legacySeedBucketsRun calls) already patched api.port/api.tls.*/auth.jwt_secret/
service_role_key, but still left api.external_url and storage.file_size_limit/
storage.vector.enabled as raw, un-overridden config values — the same class
of gap already fixed twice for this object.

- legacyResolveStorageCredentials's local branch derives Storage's base URL
  from config.api.external_url; an env/dotenv-only SUPABASE_API_EXTERNAL_URL
  override never reached bucket seeding, which could then target the wrong
  host. Folded in the already-resolved values.apiUrl.

- legacySeedBucketsRun reads config.storage.file_size_limit to fill a
  bucket's default size and config.storage.vector.enabled to gate vector
  bucket seeding; an env/dotenv-only SUPABASE_STORAGE_FILE_SIZE_LIMIT/
  SUPABASE_STORAGE_VECTOR_ENABLED override reached the real Storage
  container but not bucket seeding. Hoisted a storageVectorEnabled local
  (mirroring the existing storageFileSizeLimit hoist) and folded both into
  the resolved config's storage sub-object.

Go's newLocalClient/buckets.Run both read the single, already Viper-
overridden utils.Config for these fields — there's no separate raw-vs-
resolved distinction to preserve.
@Coly010

Coly010 commented Jul 11, 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

Coly010 commented Jul 11, 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

Coly010 commented Jul 11, 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

Coly010 commented Jul 11, 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: 0ef1d0e9d9

ℹ️ 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.live.test.ts Outdated
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts Outdated
Comment thread apps/cli/src/legacy/commands/start/lib/container-lifecycle.ts
Coly010 added 3 commits July 11, 2026 08:19
Go's isImgProxyEnabled (internal/start/start.go:302-303) requires
storage.image_transformation to be present and enabled, and both Go's and
this port's init template ship that section commented out. The live suite's
running-container assertion expected imgproxy alongside every other
default-enabled service, which would fail against a correct start; add it
to NEVER_RUNNING_SERVICE_KEYS alongside the existing supavisor entry.

review: PRRT_kwDOErm0O86QE_6S
Go's ResolveJWKS only attempts the remote fetch when issuerURL != ""
(pkg/config/config.go:1732); a provider's own issuerURL() can return the
empty string with no validation (workos's issuerURL() is a raw field read,
config.go:1631-1632), so an enabled-but-unconfigured workos provider with
auth.enabled=false is a real, Go-tolerated state. legacyResolveLocalJwks's
guard only checked issuerUrl !== undefined, so it called
resolveRemoteJwks("") and failed start in a case Go tolerates.

review: PRRT_kwDOErm0O86QE_6V
…r CLI client's own env

legacyDockerCreateContainer spawns docker create with extendEnv: true,
which merges spec.env into the spawned process's OWN environment
(prioritizing spec.env's values). Vector's spec sets a container-facing
DOCKER_HOST=http://host.docker.internal:<port> for tcp/npipe daemon hosts
(services/vector.service.ts), so that value was hijacking which daemon the
docker create call itself talks to, before the container even existed.

Go has no equivalent bug: DockerStart (internal/utils/docker.go:363-440)
calls the Engine API directly against a persistent client, never a per-call
CLI subprocess. Fixed by emitting Docker/Podman client env vars (DOCKER_HOST
and friends) inline as -e KEY=value in docker-create-args.ts instead of the
key-only form, and excluding them from the env handed to the spawned
process in container-lifecycle.ts.

review: PRRT_kwDOErm0O86QE_6Y
@Coly010

Coly010 commented Jul 11, 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.

@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: 0a4aaabf87

ℹ️ 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/services/gotrue.service.ts Outdated
Comment thread apps/cli/src/shared/auth/jwks.ts Outdated
Comment thread apps/cli/src/legacy/shared/legacy-local-config-values.ts Outdated
Coly010 added 2 commits July 11, 2026 09:30
…mitempty

Go's ToPublicJWK only ever appends to a nil KeyOps slice (pkg/config/auth.go:
126-130), so combined with the json:"key_ops,omitempty" tag (auth.go:92), Go
can emit an absent key_ops field or a non-empty array, never "key_ops":[].
key.key_ops?.filter(...) alone only returns undefined when key.key_ops itself
is undefined, not when filtering an array down to zero verify entries leaves
an empty array — that empty array was serializing as "key_ops":[], letting a
public key explicitly claim it allows no operations.

Not reachable through today's callers (LegacyJwkSchema doesn't preserve
key_ops from signing_keys_path yet, and both hardcoded defaults include
"verify"), but a real latent gap against this file's own stated Go-omitempty
serialization contract.

review: PRRT_kwDOErm0O86QFRKh
…explicit empty mailer subjects

Two related Go-parity gaps in GoTrue's env building, both flagged by review
and fixed together since they touch the same signing-key/email-resolution
code paths:

Signing key metadata (review: PRRT_kwDOErm0O86QFRKk)
Go decodes auth.signing_keys_path directly into []JWK (pkg/config/config.go:
1113), so a configured key's use/key_ops/ext round-trip into both
GOTRUE_JWT_KEYS and the published JWKS via ToPublicJWK (pkg/config/auth.go:
111-145, which keeps use/ext verbatim and filters key_ops to verify-only).
LegacyJwkSchema didn't decode these three fields at all, so Effect's
Schema.decodeUnknownSync silently stripped them from any real signing-keys
file before they ever reached LegacyGotrueSigningKey/JwkLike, both of which
already modeled all three. Added use/key_ops/ext to both LegacyJwk and
LegacyJwkSchema, matching Go's JWK struct field order; key_ops stays a
mutable string[] on LegacyJwk (unlike the ReadonlyArray elsewhere) since it's
passed straight into Node's createPrivateKey, which wants a mutable array.

Mailer subject presence (review: PRRT_kwDOErm0O86QFRKd)
Go's emailTemplate.Subject is *string (pkg/config/auth.go:266); the mailer-
env block gates strictly on subject != nil, not string length, so an
explicit subject = "" is a real, non-nil state Go still emits
GOTRUE_MAILER_SUBJECTS_*= for. @supabase/config's template/notification
schema decodes subject as a plain string defaulting to "", collapsing
"absent" and "present-but-empty" to the same value, so a length check alone
can't recover Go's presence signal. legacyResolveAuthEmail now takes the raw
auth document and resolves each entry's subject to string | undefined,
reading whether the raw TOML key is present (an env override still wins
outright, matching Viper's AutomaticEnv precedence) — the same pattern this
file already uses for the auth.captcha/passkey/webauthn/email.smtp presence
gaps. gotrue.service.ts's addMailerEnvVars now gates on subject !== undefined
instead of .length > 0.
@Coly010

Coly010 commented Jul 11, 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.

@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: 9c82751175

ℹ️ 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".

typeof error === "object" && error !== null && "error" in error ? error.error : error;
// `Effect.tryPromise`'s default failure is a `Cause.UnknownError`, which stores the
// original rejection on `.cause` (inherited from `Error`) — NOT `.error`.
const cause = error.cause;

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 Read tryPromise failures from .error

When a project has no supabase/functions directory, readdir rejects with ENOENT and Effect.tryPromise wraps the original rejection on the wrapper's .error field rather than .cause. This now sees undefined, re-fails instead of treating the directory as empty, and makes discoverFunctionSlugs abort for functions serve/deploy in projects without a functions directory; check the wrapped .error value (or provide a typed catch) before testing code === "ENOENT".

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the same claim raised and investigated in an earlier review round on this same line (see the other thread on deploy.ts:1760), so leaving this as-is with the same rationale.

Effect.tryPromise's default failure type in this repo's actual Effect version (V4/effect-smol, effect@4.0.0-beta.93) is Cause.UnknownError, which stores the original rejection on .cause (inherited from Error) — there is no .error field on this type. That's Effect V3's UnknownException shape; this repo is on the V4 rewrite where that type was renamed and the field renamed with it. git log on this file shows the code was deliberately changed from error.error to error.cause earlier in this branch's history, specifically because the old .error check never fired under V4. The current code (and its inline comment) is correct as written.

Comment thread apps/cli/src/legacy/commands/stop/stop.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 Outdated
typeof error === "object" && error !== null && "error" in error ? error.error : error;
// `Effect.tryPromise`'s default failure is a `Cause.UnknownError`, which stores the
// original rejection on `.cause` (inherited from `Error`) — NOT `.error`.
const cause = error.cause;

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 Read tryPromise failures from .error

When a project has no supabase/functions directory, readdir rejects with ENOENT and Effect.tryPromise wraps the original rejection on the wrapper's .error field rather than .cause. This now sees undefined, re-fails instead of treating the directory as empty, and makes discoverFunctionSlugs abort for functions serve/deploy in projects without a functions directory; check the wrapped .error value (or provide a typed catch) before testing `code === "ENOENT".

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the same claim raised and investigated in an earlier review round on this same line (see the other thread on deploy.ts:1760), so leaving this as-is with the same rationale.

Effect.tryPromise's default failure type in this repo's actual Effect version (V4/effect-smol, effect@4.0.0-beta.93) is Cause.UnknownError, which stores the original rejection on .cause (inherited from Error) — there is no .error field on this type. That's Effect V3's UnknownException shape; this repo is on the V4 rewrite where that type was renamed and the field renamed with it. git log on this file shows the code was deliberately changed from error.error to error.cause earlier in this branch's history, specifically because the old .error check never fired under V4. The current code (and its inline comment) is correct as written.

const binds = await Promise.all(
secretFiles.map(async (secretFile, index) => {
const hostPath = join(dir, `secret-${index}`);
await writeFile(hostPath, secretFile.content, { mode: 0o600 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make staged secret files readable by container users

On hosts where the CLI user's UID differs from the image's runtime UID, these 0600 bind-mounted secret files are owned by the host user and Docker preserves that ownership/mode inside the container. Kong's default start path now needs to read /home/kong/kong.yml from one of these files, and non-root images such as Kong/Supavisor can fail at boot with permission denied even though Go wrote the same content inside the container as the container user; stage these files with ownership or permissions the target container user can read.

Useful? React with 👍 / 👎.

Coly010 added 2 commits July 11, 2026 10:23
…ially fails

legacyDockerRemoveAll's pipeline (stop -> container prune -> conditional
volume prune -> network prune) fails-and-returns immediately on the first
failing stage. Once container prune succeeds and a later stage (volume/
network prune) fails, the matching containers are already docker rm'd, so
the plain yield* legacyCleanupStartSecrets(...) statement after the pipe
never ran, and a subsequent stop can no longer rediscover those container
names via docker ps to reclaim their .temp/start-secrets/<name> credential
directories.

start.rollback.ts's legacyRollbackStart already runs this same cleanup
unconditionally after its own legacyDockerRemoveAll call for the identical
reason; this makes stop consistent with that sibling caller, wrapping the
call in Effect.ensuring(Effect.suspend(...)) instead so cleanup always runs
off the pre-teardown onContainersListed snapshot while the original
LegacyStop*Error still propagates.

review: PRRT_kwDOErm0O86QFr7w
…cert reads on resolved api.enabled

Two Go-parity gaps in start.handler.ts, fixed together since both touch the
same config-resolution area:

Gate-only boolean overrides as Effect defects (review: PRRT_kwDOErm0O86QFr73)
legacyResolveStartGates calls legacyEnvOverrideBool for ten SUPABASE_*_ENABLED
fields, which throws synchronously on an unparsable value (matching Go's
Config.Load hard-failing on a bad Viper bool decode). The bare, unwrapped
call site let that throw escape as an untyped Effect defect instead of the
typed LegacyStartInvalidConfigError every other malformed-config path in
this handler already uses — the same bug class fixed 6+ times earlier this
session for other env-override call sites. Wrapped in Effect.try, following
the exact precedent already used for legacyResolveLocalConfigValues.

TLS cert reads ignoring resolved api.enabled (review: PRRT_kwDOErm0O86QFr70)
Go nests the whole TLS cert/key disk read inside `if c.Api.Enabled`
(pkg/config/config.go:1006-1027), not just `if c.Api.Tls.Enabled` — when API
is disabled (however that happened), Kong keeps its embedded default cert
regardless of TLS settings. There was no env-overridden api.enabled local at
all in this file; the Kong cert-read gate checked only apiTlsEnabled, and
effectiveLocalStorageConfig.api still carried the raw config.api.enabled,
which legacy-storage-credentials.ts's validateLocalKongTls gate reads
directly. Added a hoisted apiEnabled local (same legacyEnvOverrideBool
pattern as the existing apiTlsEnabled), ANDed it into the cert-read gate, and
folded it into effectiveLocalStorageConfig.api.enabled.
@Coly010

Coly010 commented Jul 11, 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.

@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: 3e5cd58c9a

ℹ️ 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".

"local_smtp.port",
projectEnvValues,
);
const mailpitSmtpPort = envOverridePort(

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 Convert late Mailpit port parse errors into config failures

When SUPABASE_LOCAL_SMTP_SMTP_PORT is malformed, this envOverridePort call runs after image pre-pull and outside an Effect.try, so it throws as an Effect defect instead of the typed LegacyStartInvalidConfigError path used for the pooler/Edge port overrides. In JSON/stream-json mode that bypasses normal error formatting, and it also does Docker image work that Go would avoid by rejecting the bad uint16 during config load; wrap these late Mailpit/analytics port override parses before continuing.

Useful? React with 👍 / 👎.

// already-overridden values, matching Go's single `utils.Config.Realtime`
// source of truth (`internal/start/start.go:922,928`,
// `internal/db/start/start.go:283,290`).
const realtimeIpVersion = legacyEnvOverrideRealtimeIpVersion(

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 Convert late Realtime/API overrides into config failures

When SUPABASE_REALTIME_IP_VERSION=IPv5 is supplied, this parser now runs after legacyEnsureImagesCached and outside Effect.try; the sibling Realtime max-header, API max-rows, and storage vector parsers in the same block have the same shape. A malformed value therefore becomes an Effect defect instead of LegacyStartInvalidConfigError, bypassing JSON error handling after Docker image work that Go would avoid by rejecting the override during config load; resolve these late service-setting overrides in a typed config-failure step before pre-pull.

Useful? React with 👍 / 👎.

const pinnedVersion = serviceVersions[localServiceName];
return pinnedVersion === undefined
? baseImage
: replaceImageTag(baseImage, tagForServiceVersion(localServiceName, pinnedVersion));

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 Preserve raw linked service version tags

When a .temp/*-version pin contains a raw tag such as storage-version = 1.2.3, Go passes the file contents directly to replaceImageTag and starts supabase/storage-api:1.2.3. This call routes the pin through tagForServiceVersion, which adds a v prefix for several services, so the native start path pulls/creates :v1.2.3 instead; use the raw trimmed pin here just like the services command's normalizeVersionTags: false path.

Useful? React with 👍 / 👎.

@Coly010

Coly010 commented Jul 11, 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.

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