Skip to content

feat(cli): opt supabase init into the experimental stack via env - #6665

Open
avallete wants to merge 5 commits into
developfrom
avallete/e66eaf50
Open

avallete wants to merge 5 commits into
developfrom
avallete/e66eaf50

Conversation

@avallete

Copy link
Copy Markdown
Member

Summary

  • SUPABASE_EXPERIMENTAL_STACK=1 supabase init writes [experimental] stack = true and omits Docker-era default ports so the new stack is not pinned to legacy local listeners.
  • Unset or 0 keeps the established Go-parity template. Invalid values fail closed. Blank supabase bootstrap uses the same scaffold.

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.
@avallete
avallete requested a review from a team as a code owner September 17, 2026 14:59
Keep the init opt-in docs next to develop's stack targeting section.
@avallete

Copy link
Copy Markdown
Member Author

/ai-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment on lines +23 to +27
const experimentalStack = yield* resolveExperimentalFeature({
feature: "stack",
configValue: Effect.succeed(false),
env: process.env,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +171 to +175
experimentalStack: yield* resolveExperimentalFeature({
feature: "stack",
configValue: Effect.succeed(false),
env: process.env,
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in fa69747. Docs now treat empty as unset/fallback, and only a non-empty value other than 0 or 1 as a failure.

Comment on lines +496 to +503
`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, "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +171 to +175
experimentalStack: yield* resolveExperimentalFeature({
feature: "stack",
configValue: Effect.succeed(false),
env: process.env,
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +469 to +471
const EXPERIMENTAL_STACK_INIT_FLAG = `# Use the new local stack backend for top-level start, stop, and status.
stack = true
`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.
@avallete
avallete enabled auto-merge September 17, 2026 18:20
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