feat(cli): select stack start and stop with feature flag - #6544
Conversation
There was a problem hiding this comment.
🤖 AI Review
The PR has one critical compatibility break: the published experimental.stack key is rejected by the bundled Go sidecar, breaking delegated commands for users who enable the documented setting. Confirmed minor issues include incorrect completion routing, silently discarded completion-routing errors, help depending on valid config, inconsistent boolean-env parsing, duplicate root-tree construction, and command ordering. Three Claude findings were refuted because the documentation already states the alias-only scope, the resolver branches are covered and its exported error is required by trusted conventions, and the backend remains explicitly experimental despite its renamed command path.
Findings
| Severity | Location | Category | Sources | Claim |
|---|---|---|---|---|
| 🔴 CRITICAL | packages/config/src/experimental.ts:35 |
compatibility |
claude | The new experimental.stack key is absent from the bundled Go config model, so Go-delegated commands that load a config containing it fail UnmarshalExact. |
| 🟡 MINOR | apps/cli/src/command-internal/experimental-feature.ts:28 |
consistency |
claude | SUPABASE_EXPERIMENTAL_STACK rejects conventional boolean spellings such as true and false, unlike related config boolean overrides. |
| 🟡 MINOR | apps/cli/src/cli/root.ts:93 |
cli-parity |
claude | stackCommand is declared out of alphabetical order, affecting declaration-order consumers such as help and generated documentation. |
| 🟡 MINOR | apps/cli/src/commands/experimental/stack/stack-backend.ts:100 |
error-handling |
claude | Top-level start and stop help now requires routing config to parse successfully, so malformed config prevents --help from rendering. |
| 🟡 MINOR | apps/cli/src/cli/main.ts:26 |
error-handling |
claude+codex | Completion routing failures are silently discarded, and the new entrypoint failure wiring lacks direct coverage. |
| 🟡 MINOR | apps/cli/src/cli/main.ts:23 |
performance |
claude | Normal invocations construct the full root command tree twice. |
| 🟡 MINOR | apps/cli/src/commands/experimental/stack/stack-backend.ts:103 |
completion-routing |
codex | Hidden completion argv is interpreted as execution argv, causing root completion to read config and help-path completion to select the wrong backend. |
| ⚪ NIT | apps/cli/src/commands/experimental/stack/stack-backend.ts:69 |
error-handling |
claude | TOML syntax errors are mislabeled as config read failures. |
| ⚪ NIT | apps/cli/src/docs/docs-spec.tables.ts:143 |
style |
claude | The new default-override entries violate the table's alphabetical ordering. |
| ⚪ NIT | apps/cli/src/cli/root.ts:71 |
observability |
claude | Top-level stack aliases emit the legacy start/stop command identity without any telemetry field identifying the selected backend. |
| ⚪ NIT | apps/cli/src/commands/experimental/stack/stack-command-telemetry.integration.test.ts:26 |
style |
claude | The test imports a sibling module through a needlessly roundabout path. |
| ⚪ NIT | apps/docs/public/cli/config.schema.json:996 |
maintainability |
claude | The generated schema changes include unrelated pre-existing source drift. |
Refuted findings (kept for transparency, not posted as review comments)
apps/cli/docs/stack-commands.md:44(documentation): The documentation fails to disclose that onlystartandstopare switched, leaving users with an undocumented split between local backends.
Refuted: The cited guide explicitly discloses both the alias-only scope and separate backend state. Enumerating every unaffected local command could improve the guide but is not an undisclosed behavior.apps/cli/src/command-internal/experimental-feature.ts:8(test-coverage): The exported error and shared experimental resolver are unused or inadequately tested.
Refuted: The export is a trusted repository requirement, not dead API, and the integration suite covers the resolver's relevant branches through its production caller.apps/cli/src/commands/experimental/stack/stack-config.ts:707(documentation): The phraseexperimental stackis stale now that the command moved tosupabase stack.
Refuted: Removingexperimentalfrom the invocation path does not establish that the backend itself is no longer experimental; the feature remains explicitly controlled by the experimental config section.
Stats
Claude findings: 14 · Codex findings: 2 · Confirmed: 12 · Refuted: 3 · Uncertain: 0
Models: claude-opus-5 + gpt-5.6-sol · Trigger: auto · Workflow run
This review runs once per PR. A maintainer can request another with a /ai-review comment.
Coly010
left a comment
There was a problem hiding this comment.
Ran this through parallel architecture, implementation, and DX reviews. The pre-parse backend-selection mechanism itself (lazy config reads, typed routing errors, completion-cursor handling, preserved telemetry identity) is well-built. The problems are all in what happens once someone actually flips experimental.stack = true — inline comments below cover the specific spots, and this summarizes what doesn't map to a single line:
No signposting when the backend switches. supabase start/stop with the flag on produce a completely different output shape (a Stack <id> / Runtime / Lifecycle block instead of the usual API URL / anon key table) with nothing printed anywhere saying "this is the experimental stack backend" or "your legacy stack's data is untouched." A user flipping the flag has no way to tell this from data loss.
The rest of local dev doesn't follow the flag. status, db reset, and db diff stay bound to the legacy Docker stack regardless of experimental.stack, so with the flag on, supabase start (new stack) followed by supabase status (legacy) reports nothing running. None of this is documented in docs/stack-commands.md, which only mentions status in passing.
stack stop doesn't know about a legacy stack from the same project. If a legacy stack is running when the flag gets flipped on, supabase stop (now routed to the stack backend) exits 0 having found nothing to do, while the legacy containers keep the port bound — so the next supabase start fails with a PortUnavailableError that never mentions the other backend is the actual cause.
Docs ordering. docs/stack-commands.md buries the "backends don't share data" caveat at the bottom in defensive phrasing ("does not import, copy, seed from, or reuse") instead of leading with it, and references a command path (supabase experimental stack) that only ever existed in an unreleased beta — likely to confuse rather than help.
Scope creep. The 14 apps/cli-go/pkg/config/testdata/*.diff fixture updates plus the db_test.go/updater_test.go changes fix pre-existing drift (a db_allowed_cidrs→allowed_cidrs rename, a swapped diff direction, a deleted gock stub) unrelated to stack routing — worth splitting into its own PR so a reviewer of a start/stop feature flag isn't also on the hook for auth/network-restrictions fixture correctness.
Given the number of user-facing regressions once the flag is actually used, I'd want at minimum the flag-stripping diagnostic, the --help/malformed-config interaction, and the config-loader bypass (comments below) resolved before merge.
|
Thanks for the detailed review. This PR is the foundation for the new local stack: status and the remaining local-stack commands will migrate in follow-up PRs before the feature flag is promoted. Backend-specific arguments and flags are intentional, and each backend continues to own its own resources. We are not adding temporary cross-backend compatibility behavior or migration banners for this intermediate state. This revision fixes short-cluster routing, defaults unresolved config-based routing to legacy, and clarifies experimental status and configuration reads. Local TOML-only loading, remote-selection behavior, and the 1|0 environment contract remain unchanged for the reasons in the inline replies. The fixture-only Go repairs are retained so the full nested config module suite passes; they align stale fixtures with existing serializers and do not change runtime behavior. |
Expose the new local runtime as
supabase stack start|stop. Projects can opt top-levelstartandstopinto that runtime with[experimental] stack = trueinsupabase/config.toml, or override it withSUPABASE_EXPERIMENTAL_STACK=1|0. Selection happens before argument parsing and completion, preserving each implementation’s flags and the invoked command’s telemetry identity. Unreadable or invalid routing config defaults to the legacy command tree; explicit environment overrides take precedence, and each command retains its own execution-time config validation.The shared experimental flag resolver is reusable for the later workers migration.
statuskeeps its existing implementation andexperimental workersremains available. The two stack backends retain separate state and databases; enabling the flag does not migrate existing data. The flag is excluded from hosted project configuration and accepted as a TypeScript-owned field by the Go sidecar.Extracts the start/stop portion of #6516 onto
developafter #6507, so it can merge independently of the remaining stack commands.Regenerates the published configuration schemas, including previously stale auth fields already present in the source schemas. Refreshes stale Go config fixtures to match the current serializer and schema.