Skip to content

feat(cli): add experimental stack status - #6508

Open
jgoux wants to merge 17 commits into
developfrom
feat/experimental-stack-status
Open

feat(cli): add experimental stack status#6508
jgoux wants to merge 17 commits into
developfrom
feat/experimental-stack-status

Conversation

@jgoux

@jgoux jgoux commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

supabase stack status reports the selected stack's identity, runtime, owner, readiness, endpoints, and configuration drift. It loads configuration from the selected stack's project, including when inspecting an ID from another working directory.

Adds a read-only configuration comparison to the stack package that reports changed paths without exposing secret values. Invalid or unreadable project configuration leaves inspection available and marks drift as unavailable. An absent config.toml is compared against default settings, matching what stack start does (develop's project loader treats an absent file as defaults, not an error).

status --env

Ported from #6517 (closed):

  • status --env exports connection variables as dotenv (text) or a JSON variable map (--output-format json / stream-json). Ordinary status never reveals credentials.
  • --override-name API_URL=NEXT_PUBLIC_SUPABASE_URL renames exported variables (CSV or repeated; requires --env; rejects unknown sources, invalid names, and duplicate destinations).
  • Database-only stacks export database credentials with API credentials omitted when Auth is disabled. stack.credentials() in @supabase/stack now returns an optional api instead of failing when Auth is disabled.
  • A stopped stack or credential failure produces an error with no partial output.
  • The legacy supabase status -o env form is rejected with a pointer to --env.

Feature-flag routing

Top-level supabase status now follows [experimental] stack = true / SUPABASE_EXPERIMENTAL_STACK exactly like start and stop, so a project on the new backend gets a status command that understands its stack. supabase stack status always uses the new backend.

Also in this PR

  • Merged develop (resolved EffectStack.ts conflict from refactor(stack): simplify stack identity #6531's descriptor(state, id) change).
  • Fixed CI: lint:effect:check warnings in the drift test, the error-tag telemetry fixture, and the routing test's hardcoded stack subcommand list.

@jgoux
jgoux requested a review from a team as a code owner September 7, 2026 23:36
@jgoux
jgoux force-pushed the feat/experimental-stack-status branch from e120ca7 to 1764f96 Compare September 8, 2026 05:31
@jgoux
jgoux force-pushed the feat/experimental-stack-status branch 2 times, most recently from fa05922 to 34ca600 Compare September 8, 2026 12:32
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@34ca600fdad2a3cbe8f166998f05d4b6fdbc4d6f

Preview package for commit 34ca600.

@jgoux

jgoux commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review

Base automatically changed from feat/experimental-stack-stop to develop September 9, 2026 11:37
@avallete
avallete force-pushed the feat/experimental-stack-status branch from 34ca600 to 9ad64f6 Compare September 11, 2026 11:14
@jgoux

jgoux commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

On this one, can we integrate :

status --env exports connection variables as dotenv or a JSON variable map, with --override-name for application-specific names. Ordinary status does not reveal credentials. Database-only stacks can retrieve database credentials with API credentials omitted when Auth is disabled.

It was done here but I'm closing this PR as well and porting what was done for start/stop/destroy directly from develop.

Database-only stacks previously failed credentials() with an
InvalidStackConfigError. The api credentials are now optional and are
omitted when the Auth capability is disabled.
Suppress the JSON serialization lint in the redaction assertions of the
drift test and add ExperimentalStackStatusError to the error-tag
fixture.
status --env exports connection variables as dotenv or a JSON variable
map, with --override-name for application-specific names. Ordinary
status does not reveal credentials. Database-only stacks export database
credentials with API credentials omitted when Auth is disabled.

An absent config.toml is compared against default settings, matching
stack start; invalid configuration still reports drift as unavailable.
The legacy -o env form is rejected with a pointer to --env.
supabase status follows [experimental] stack and
SUPABASE_EXPERIMENTAL_STACK like start and stop, so a project on the new
backend gets a status command that understands its stack.
output.success merges message into the JSON payload, so --env produced
{ DB_URL, message: "" }. Use output.result for the bare variable map and
assert the serialized shape in the integration test.
@Coly010

Coly010 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

/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

Verified all 11 reported findings against the PR code and trusted repository conventions. One duplicate was merged, yielding 10 findings: five minor and five nit-level. All are confirmed; no critical or major issues were found.

Findings

Severity Location Category Sources Claim
🟡 MINOR apps/cli/src/commands/experimental/stack/status/status.errors.ts:16 telemetry claude The runtime reason is incorrectly classified as an external-service network failure even when it represents user-actionable lifecycle or local encoding errors.
🟡 MINOR apps/cli/src/commands/experimental/stack/status/status.handler.ts:229 error-handling claude Configuration comparison errors discard their precise typed diagnostic and inaccurately report that the project configuration could not be loaded.
🟡 MINOR apps/cli/docs/stack-commands.md:31 documentation claude The migration guide mentions rejection of legacy -o env but omits that every explicit legacy -o/--output value, including json and pretty, is rejected by the stack backend.
🟡 MINOR apps/cli/src/commands/experimental/stack/status/status.env.ts:84 correctness codex Text export rejects values containing both a single quote and a backtick even when they can be represented losslessly with dotenv double quotes.
🟡 MINOR apps/cli/src/commands/experimental/stack/status/status.env.ts:39 correctness codex Override collision validation includes optional variables that will not be exported, rejecting non-colliding renames on capability-limited stacks.
⚪ NIT packages/config/src/experimental.ts:44 documentation claude The generated public configuration schemas retain the old description that says the stack backend affects only start and stop.
⚪ NIT apps/cli/src/commands/experimental/stack/status/status.env.ts:37 validation claude Conflicting repeated overrides for the same source variable are silently resolved last-wins.
⚪ NIT apps/cli/src/commands/experimental/stack/status/status.handler.ts:213 code-quality claude The environment-export branch returns an unrelated optional inspection whose presence depends solely on whether --stack-id was used.
⚪ NIT apps/cli/src/commands/experimental/stack/status/status.handler.ts:226 performance claude Status with --stack-id and a loadable configuration inspects the stack twice, repeating the owner status RPC.
⚪ NIT apps/cli/src/commands/experimental/stack/status/SIDE_EFFECTS.md:49 documentation claude+codex The side-effect contract omits the command's exit-code behavior and telemetry-state write.

Stats

Claude findings: 8 · Codex findings: 3 · Confirmed: 10 · Refuted: 0 · 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.

}> {
get [ErrorActionabilityId](): CliErrorActionabilityDeclaration {
if (this.reason === "flags" || this.reason === "not-found") return actionability.provideFlags;
return this.reason === "runtime" ? actionability.externalNetwork : actionability.invalidConfig;

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 · telemetry · source: claude

The runtime reason is incorrectly classified as an external-service network failure even when it represents user-actionable lifecycle or local encoding errors.

Evidence: status.errors.ts:16 maps every runtime error to actionability.externalNetwork. status.handler.ts:203-208 uses that reason when the stack is stopped, and status.env.ts:85-90 uses it for dotenv encoding failures. error-actionability.ts:355-360 classifies the preset as ExternalService/Network.

Suggested fix: Split lifecycle, encoding, and genuine runtime failures into distinct reasons and map the user-actionable cases to appropriate non-network actionability presets.

Comment on lines +229 to +232
InvalidStackConfigError: () =>
Effect.succeed({ inspection: undefined, warning: configUnavailableWarning }),
StackVersionUnsupportedError: () =>
Effect.succeed({ inspection: undefined, warning: configUnavailableWarning }),

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

Configuration comparison errors discard their precise typed diagnostic and inaccurately report that the project configuration could not be loaded.

Evidence: status.handler.ts:215-219 uses configUnavailableWarning for actual loading failures, while lines 228-233 reuse the same warning for InvalidStackConfigError and StackVersionUnsupportedError. Compiler.ts:468-472 supplies useful diagnostics such as Unsupported database version: ....

Suggested fix: Use a distinct, safely sanitized warning for comparison rejection that preserves the typed error's actionable diagnostic.

accepts repeated or comma-separated `EXPORTED_VARIABLE=NAME` entries, requires `--env`, and rejects
unknown variables, invalid names, and collisions. API credentials are omitted when Auth is disabled.

The legacy `supabase status -o env` form is rejected on the stack backend; use `--env` instead.

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 migration guide mentions rejection of legacy -o env but omits that every explicit legacy -o/--output value, including json and pretty, is rejected by the stack backend.

Evidence: stack-commands.md:31 discusses only -o env, whereas stack.shared.ts:109-121 rejects any populated legacy output flag and status.integration.test.ts:428-436 explicitly verifies rejection of json.

Suggested fix: Document that the entire legacy -o/--output flag is unsupported and list the corresponding --output-format replacements.

Schema.Boolean.annotate({
description: "Use the new local stack backend for top-level start and stop commands.",
description:
"Use the new local stack backend for top-level start, stop, and status commands.",

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 public configuration schemas retain the old description that says the stack backend affects only start and stop.

Evidence: experimental.ts:43-44 includes status, but apps/docs/public/cli/config.schema.json:2409 and :4916 and project-config.schema.json:1947 still say top-level start and stop commands. Trusted CLI conventions explicitly require generated schemas to be updated with experimental registration changes.

Suggested fix: Regenerate and commit the public CLI configuration schemas.

message:
"--override-name must be EXPORTED_VARIABLE=VALID_ENV_NAME; for example API_URL=NEXT_PUBLIC_SUPABASE_URL.",
});
names.set(source, target);

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 · validation · source: claude

Conflicting repeated overrides for the same source variable are silently resolved last-wins.

Evidence: status.env.ts:23-38 unconditionally replaces the map entry for each source. The later uniqueness check only checks final destination values, so API_URL=FIRST followed by API_URL=SECOND succeeds and discards FIRST.

Suggested fix: Reject an override when its source variable has already appeared in the supplied entries.

const values = stackEnvValues(status, credentials, envNames);
if (output.format === "text") yield* output.raw(yield* encodeStackEnv(values));
else yield* output.result(values);
return target.inspection;

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 · code-quality · source: claude

The environment-export branch returns an unrelated optional inspection whose presence depends solely on whether --stack-id was used.

Evidence: status.handler.ts:213 returns target.inspection; findDescriptor populates that field only in the explicit-id branch at lines 141-149. The command wrapper at status.command.ts:43-45 does not consume the success value.

Suggested fix: Return void or the exported values from the environment branch and narrow the handler's success type accordingly.

? target.inspection === undefined
? yield* catchStackError(api.inspectStack(target.id)).pipe(Effect.map(comparedInspection))
: { inspection: target.inspection }
: yield* api.inspectStack(target.id, { config: loaded.config }).pipe(

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 · performance · source: claude

Status with --stack-id and a loadable configuration inspects the stack twice, repeating the owner status RPC.

Evidence: findDescriptor calls inspectStack at status.handler.ts:143, then the comparison calls it again at line 226. status.integration.test.ts:230-239 asserts two inspections. EffectStack.ts:1254-1262 performs an owner rpc.status during each live-owner inspection.

Suggested fix: Expose a lightweight persisted-descriptor lookup or otherwise arrange for target-root resolution and config comparison to require only one full inspection.


Ordinary status (without `--env`) never opens a stack handle and never emits
credentials, regardless of the stack's lifecycle. A stopped stack or a
credentials failure with `--env` fails the command without emitting output.

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+codex

The side-effect contract omits the command's exit-code behavior and telemetry-state write.

Evidence: SIDE_EFFECTS.md ends at line 49 without exit-code or telemetry persistence sections. status.handler.ts:245 ensures telemetryState.flush on success and failure. The trusted SIDE_EFFECTS template requires exit paths and telemetry events, and sibling stack contracts document the telemetry file.

Suggested fix: Document all exit paths, command telemetry, and the telemetry-state flush to <SUPABASE_HOME or ~/.supabase>/telemetry.json.

Comment on lines +84 to +90
const quote = ["'", "`"].find((candidate) => !value.includes(candidate));
if (quote === undefined || value.includes("\r"))
return Effect.fail(
new StackCommandStatusError({
reason: "runtime",
message:
"A credential cannot be represented losslessly as dotenv. Use --env --output-format json.",

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 · correctness · source: codex

Text export rejects values containing both a single quote and a backtick even when they can be represented losslessly with dotenv double quotes.

Evidence: status.env.ts:84 considers only single-quote and backtick delimiters, then lines 85-90 fail when both occur. The test's example both'andquotes` contains neither a double quote nor a double-quote escape sequence and is valid as a double-quoted dotenv value.

Suggested fix: Add a double-quoted encoding path when it round-trips losslessly through dotenv, retaining rejection only for genuinely unrepresentable values.

Comment on lines +39 to +42
if (new Set(names.values()).size !== names.size)
return yield* new StackCommandStatusError({
reason: "flags",
message: "--override-name produces duplicate environment variable names.",

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 · correctness · source: codex

Override collision validation includes optional variables that will not be exported, rejecting non-colliding renames on capability-limited stacks.

Evidence: status.env.ts:22 initializes mappings for all canonical variables and lines 39-43 validate uniqueness before status or credentials are known. Lines 54-72 later omit API, endpoint, and storage variables when unavailable, so a target collision with one of those absent variables cannot affect the actual output.

Suggested fix: Validate destination collisions against the source variables actually present after status and credentials have been loaded.

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.

3 participants