Conversation
SUPABASE_EXPERIMENTAL_STACK=1 writes experimental.stack and omits Docker-era default ports so new projects can use the stack without pinning legacy local ports.
Keep the init opt-in docs next to develop's stack targeting section.
|
/ai-review |
There was a problem hiding this comment.
🤖 AI Review
Adjudication found eight confirmed concerns and refuted two. Most are documentation, robustness, and coverage issues; the most significant is that persisting the environment opt-in conflicts with the trusted repository convention that environment opt-ins must not write project configuration.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🟠 MAJOR | apps/cli/src/commands/init/init.handler.ts:23 |
behavior-regression |
codex | The new init and blank-bootstrap behavior turns a temporary environment override into persistent project configuration, contrary to the trusted repository convention that environment opt-ins do not write project configuration. |
| 🟡 MINOR | apps/cli/src/commands/bootstrap/bootstrap.handler.ts:171 |
error-handling |
claude | Blank bootstrap validates SUPABASE_EXPERIMENTAL_STACK only after creating the work directory, possibly prompting for overwrite, and changing the process working directory. |
| 🟡 MINOR | apps/cli/src/commands/init/SIDE_EFFECTS.md:33 |
documentation |
claude | The init environment-variable table omits SUPABASE_YES even though the handler reads it through resolveYes. |
| 🟡 MINOR | apps/cli/src/commands/init/SIDE_EFFECTS.md:41 |
documentation |
codex | The documented invalid-value condition incorrectly includes an empty SUPABASE_EXPERIMENTAL_STACK value, although the resolver treats an empty value as unset. |
| 🟡 MINOR | apps/cli/src/commands/bootstrap/bootstrap.handler.ts:171 |
test-coverage |
claude | The newly added bootstrap environment-flag wiring has no handler integration coverage for either enabled scaffolding or invalid input. |
| ⚪ NIT | apps/cli/src/shared/init/project-init.templates.ts:496 |
robustness |
claude | The stack template transformation relies on brittle substring replacements, including an analytics port fragment with no section or comment anchor. |
| ⚪ NIT | apps/cli/src/shared/init/project-init.templates.ts:469 |
documentation |
claude | The generated comment describes only top-level start, stop, and status even though experimental.stack also controls numerous local database, migration, storage, seed, and inspection paths. |
| ⚪ NIT | apps/cli/src/commands/bootstrap/SIDE_EFFECTS.md:72 |
formatting |
claude+codex | The newly added SIDE_EFFECTS table rows are not padded consistently with the surrounding aligned Markdown tables. |
Refuted findings (kept for transparency, not posted as review comments)
apps/cli/src/shared/init/project-init.ts:139(api-design): Making experimentalStack optional allows callers to omit an explicit scaffolding decision.
Refuted: Concrete surrounding API behavior establishes false as the intentional default for this opt-in: renderCliConfigTemplate defaults experimentalStack to false at project-init.templates.ts:508-517, and the existing non-production file-mode caller deliberately omits it. Both production handlers pass an explicit value. Optionality therefore encodes the established opt-in default rather than an ambiguous runtime state.apps/cli/src/commands/init/init.handler.ts:25(correctness): An unset environment variable during init --force drops an existing experimental.stack=true setting.
Refuted: The force path intentionally replaces the entire configuration rather than preserving selected existing settings: project-init.ts:296-308 only short-circuits without force and otherwise writes a fresh template, while init/SIDE_EFFECTS.md:92 and stack-commands.md:90-93 explicitly document replacement and the no-environment legacy template. Preserving this one field would conflict with the established force semantics.
Stats
Claude findings: 8 · Codex findings: 3 · Confirmed: 8 · Refuted: 2 · Uncertain: 0
Models: claude-opus-5 + gpt-5.6-sol · Trigger: manual · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
| const experimentalStack = yield* resolveExperimentalFeature({ | ||
| feature: "stack", | ||
| configValue: Effect.succeed(false), | ||
| env: process.env, | ||
| }); |
There was a problem hiding this comment.
🟠 MAJOR · behavior-regression · source: codex
The new init and blank-bootstrap behavior turns a temporary environment override into persistent project configuration, contrary to the trusted repository convention that environment opt-ins do not write project configuration.
Evidence: apps/cli/src/commands/init/init.handler.ts:23-39 resolves SUPABASE_EXPERIMENTAL_STACK and passes it into initProject; apps/cli/src/shared/init/project-init.ts:304-308 then writes a template containing experimental.stack and omitting ports. trusted/apps/cli/CLAUDE.md:84-93 explicitly states that environment opt-ins do not write project configuration.
Suggested fix: Keep the environment override transient. If persistent stack scaffolding is required, expose an explicit init option whose persistence is clear to users rather than deriving it from SUPABASE_EXPERIMENTAL_STACK.
There was a problem hiding this comment.
Intentional for this PR: init / blank bootstrap is scaffolding, not a runtime overlay. SUPABASE_EXPERIMENTAL_STACK=1 is the opt-in that writes [experimental] stack = true into the new project so later commands can keep using the normal config/env precedence.
That exception is already recorded in apps/cli/AGENTS.md (experimental feature registration). A dedicated --stack flag would be a second public switch for the same thing; not adding one here.
| experimentalStack: yield* resolveExperimentalFeature({ | ||
| feature: "stack", | ||
| configValue: Effect.succeed(false), | ||
| env: process.env, | ||
| }), |
There was a problem hiding this comment.
🟡 MINOR · error-handling · source: claude
Blank bootstrap validates SUPABASE_EXPERIMENTAL_STACK only after creating the work directory, possibly prompting for overwrite, and changing the process working directory.
Evidence: apps/cli/src/commands/bootstrap/bootstrap.handler.ts:130 creates the directory, lines 138-151 may prompt, and line 154 changes cwd; the resolver is not evaluated until lines 171-175.
Suggested fix: After selecting the starter, resolve the variable before filesystem mutation when the selected starter is blank. Do not validate it on downloaded-template paths where it is inapplicable.
There was a problem hiding this comment.
Fixed in fa69747. Blank/scratch now resolves SUPABASE_EXPERIMENTAL_STACK after template selection and before mkdir/chdir. Downloaded starters skip that read entirely.
| None. | ||
| | Variable | Purpose | Required? | | ||
| | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | ||
| | `SUPABASE_EXPERIMENTAL_STACK` | when `1`, persist `[experimental] stack = true` and omit Docker-era default ports from `supabase/config.toml`; when `0` or unset, write the established template | no | |
There was a problem hiding this comment.
🟡 MINOR · documentation · source: claude
The init environment-variable table omits SUPABASE_YES even though the handler reads it through resolveYes.
Evidence: apps/cli/src/commands/init/init.handler.ts:34-36 invokes resolveYes; apps/cli/src/command-internal/global-flags.ts:209-215 reads SUPABASE_YES. apps/cli/src/SIDE_EFFECTS_TEMPLATE.md:58-59 requires every directly or indirectly read environment variable to be listed.
Suggested fix: Add a SUPABASE_YES row describing its effect on interactive IDE prompts.
There was a problem hiding this comment.
Added a SUPABASE_YES row on the init env table in fa69747. It auto-accepts -i IDE prompts, same as --yes.
| | ---- | ------------------------------------------------------------------------------------ | | ||
| | `0` | success - prints "Finished supabase init." | | ||
| | `1` | `supabase/config.toml` already exists and `--force` was not provided | | ||
| | `1` | `SUPABASE_EXPERIMENTAL_STACK` is set to a value other than `0` or `1` | |
There was a problem hiding this comment.
🟡 MINOR · documentation · source: codex
The documented invalid-value condition incorrectly includes an empty SUPABASE_EXPERIMENTAL_STACK value, although the resolver treats an empty value as unset.
Evidence: apps/cli/src/commands/init/SIDE_EFFECTS.md:41 and 82 describe every set value other than 0 or 1 as invalid; apps/cli/src/command-internal/experimental-feature.ts:81-83 treats both undefined and the empty string as fallback cases.
Suggested fix: Say that any non-empty value other than 0 or 1 fails, and document that an empty value uses the fallback template.
There was a problem hiding this comment.
Fixed in fa69747. Docs now treat empty as unset/fallback, and only a non-empty value other than 0 or 1 as a failure.
| `port = 54327 | ||
| `, | ||
| ] as const; | ||
|
|
||
| function applyExperimentalStackInitTemplate(source: string): string { | ||
| let next = source.replace("[experimental]\n", `[experimental]\n${EXPERIMENTAL_STACK_INIT_FLAG}`); | ||
| for (const block of STACK_INIT_OMITTED_PORT_BLOCKS) { | ||
| next = next.replace(block, ""); |
There was a problem hiding this comment.
⚪ NIT · robustness · source: claude
The stack template transformation relies on brittle substring replacements, including an analytics port fragment with no section or comment anchor.
Evidence: apps/cli/src/shared/init/project-init.templates.ts:496-497 matches only "port = 54327\n", and lines 500-503 use first-occurrence String.replace calls without checking their match locations or counts.
Suggested fix: Anchor each removal to its section and assert that every expected transformation matched exactly once, or perform structured TOML edits.
There was a problem hiding this comment.
Leaving the string transforms. They are anchored to the Go-parity template comments plus the unique analytics port = 54327 line, and project-init.templates.unit.test.ts asserts every omitted port and the inserted stack = true block. Structured TOML would drop those comments.
| experimentalStack: yield* resolveExperimentalFeature({ | ||
| feature: "stack", | ||
| configValue: Effect.succeed(false), | ||
| env: process.env, | ||
| }), |
There was a problem hiding this comment.
🟡 MINOR · test-coverage · source: claude
The newly added bootstrap environment-flag wiring has no handler integration coverage for either enabled scaffolding or invalid input.
Evidence: apps/cli/src/commands/bootstrap/bootstrap.integration.test.ts:270-289 exercises blank scaffolding but never sets or asserts SUPABASE_EXPERIMENTAL_STACK; repository searches find coverage only in init and template tests.
Suggested fix: Extend the blank-bootstrap integration coverage to assert stack scaffolding with value 1 and the typed failure for an invalid value.
There was a problem hiding this comment.
Added in fa69747: scratch path with =1 writes the stack template; invalid =yes fails with ExperimentalFeatureFlagError before config.toml exists; downloaded templates ignore the env.
| const EXPERIMENTAL_STACK_INIT_FLAG = `# Use the new local stack backend for top-level start, stop, and status. | ||
| stack = true | ||
| `; |
There was a problem hiding this comment.
⚪ NIT · documentation · source: claude
The generated comment describes only top-level start, stop, and status even though experimental.stack also controls numerous local database, migration, storage, seed, and inspection paths.
Evidence: apps/cli/src/shared/init/project-init.templates.ts:469 mentions only top-level start, stop, and status; packages/config/src/experimental.ts:41-45 and apps/cli/docs/stack-commands.md:95-109 document the wider command scope.
Suggested fix: Use a concise description covering all local-stack-backed commands or point users to the maintained stack-command documentation.
There was a problem hiding this comment.
Updated the generated comment in fa69747 to match the experimental.stack schema description (start/stop/status plus local db/migration/test/gen/inspect/pull).
| | `SUPABASE_ACCESS_TOKEN` | auth bypass for ensure-login | no | | ||
| | `SUPABASE_PROFILE` | profile name/path (env → `~/.supabase/profile` → `supabase`) | no | | ||
| | `SUPABASE_YES` | auto-confirm the native push step's prompts, read project-`.env`-aware like the standalone `db push` | no | | ||
| | `SUPABASE_EXPERIMENTAL_STACK` | blank/`scratch` path only; when `1`, the `initProject` scaffold persists `[experimental] stack = true` and omits Docker-era default ports. Invalid values fail closed | no | |
There was a problem hiding this comment.
⚪ NIT · formatting · source: claude+codex
The newly added SIDE_EFFECTS table rows are not padded consistently with the surrounding aligned Markdown tables.
Evidence: apps/cli/src/commands/bootstrap/SIDE_EFFECTS.md:72 ends the Purpose cell before the established column width; apps/cli/src/commands/init/SIDE_EFFECTS.md:33 has the same mismatch.
Suggested fix: Align the touched rows manually with their table columns.
There was a problem hiding this comment.
Realigned the touched env-table rows in fa69747 after the copy updates.
Init tests were mutating SUPABASE_EXPERIMENTAL_STACK, which leaked into parallel integration files and could take pull off the mocked pg-delta path.
…writes Validate SUPABASE_EXPERIMENTAL_STACK on the scratch path before mkdir/chdir, cover that in integration tests, and document empty-string fallback.
Summary
SUPABASE_EXPERIMENTAL_STACK=1 supabase initwrites[experimental] stack = trueand omits Docker-era default ports so the new stack is not pinned to legacy local listeners.0keeps the established Go-parity template. Invalid values fail closed. Blanksupabase bootstrapuses the same scaffold.