feat(cli): port db dump, query, and schema declarative to native TypeScript#5586
Conversation
Supabase CLI previewnpx --yes https://pkg.pr.new/supabase/cli/supabase@c67b17b08361ceabe34094c393c6ccdf00fe394fPreview package for commit |
There was a problem hiding this comment.
💡 Codex Review
cli/apps/cli/src/legacy/commands/db/dump/dump.command.ts
Lines 66 to 69 in 8299115
Go registers this as StringSliceVarP, so --schema public,storage becomes two schemas before the dump env joins them with |; here it remains a single value (public,storage), so pg_dump receives a schema pattern that does not match the requested schemas. The same parser shape is used for the new declarative schema filters, so comma-separated schema lists regress from the Go CLI.
ℹ️ 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".
|
🤖 pr-autopilot — correction + fix: the My earlier framing here ("environmental flake, not a code defect") was wrong — correcting the record. Why it failed only on this PR: this PR flips What actually differed (verified): Go streams Docker image-pull progress to stderr via the Docker API + Fix (pushed, |
Reproduce cobra's MarkFlagsMutuallyExclusive ValidateFlagGroups errors, byte-for-byte, before any side effects run (cobra validates flag groups before PreRunE/RunE): - db query: --db-url/--linked/--local (apps/cli-go/cmd/db.go:526). Previously the handler silently branched on --linked, risking a query against a different database than intended. - db schema declarative generate: --db-url/--linked/--local (apps/cli-go/cmd/db_schema_declarative.go:499). Previously an arbitrary precedence ternary picked --local and ignored the others. - db schema declarative sync: --apply/--no-apply (apps/cli-go/cmd/db_schema_declarative.go:490). Previously --no-apply won silently in the apply-decision helper. Matches the existing inline pattern in inspect db / test db / db dump. review: PR #5586 threads (query/generate/sync mutually-exclusive targets)
The success PostRun line (Go's fmt.Println → stdout, apps/cli-go/cmd/db_schema_declarative.go:93) was written via output.raw unconditionally, so under --output-format json/stream-json it corrupted the machine payload on stdout. Gate it on output.format: keep the raw line on stdout in text mode (Go parity), emit a structured output.success in json/stream-json modes (CLI-1546: machine stdout is payload-only). Mirrors the backups/list gating pattern. review: PR #5586 thread (avoid raw stdout in JSON output mode)
Go's ToPostgresURL appends every pgconn RuntimeParams entry to the query string (apps/cli-go/internal/utils/connect.go:30-33). The TS builder dropped them, emitting only connect_timeout, so the Supavisor pooler tenant-routing `options=reference=<ref>` never reached pg-delta and declarative generate could connect to the wrong tenant on pooler fallback. Append `options` using a Go url.QueryEscape-faithful encoder (space -> +). review: PR #5586 thread (preserve pooler startup options in pg-delta URLs)
…ails When applying the generated migration fails, the user accepts reset, and `db reset --local` also fails, Go returns resetErr — the failure that blocked recovery — not the original apply error (apps/cli-go/cmd/db_schema_declarative.go:414-423). The TS port returned the apply error, hiding the reset failure. Return a reset error and include its detail in the "Database reset also failed: …" line. review: PR #5586 thread (return the reset failure after reset also fails)
Two Go-parity fixes for the declarative pg-delta edge-runtime container: - --network-id: Go's DockerStart overrides the network mode (host included) with --network-id when set (apps/cli-go/internal/utils/docker.go:267-271). The layer hardcoded host networking, so declarative runs couldn't reach the local stack on custom networks. Resolve LegacyNetworkIdFlag and pass a named network, mirroring db dump / gen types / test db. - deno_version: Go switches the edge-runtime image to the deno1 tag when [edge_runtime].deno_version = 1 (apps/cli-go/pkg/config/config.go:999-1008). The layer hardcoded the default (2). Surface deno_version from legacyReadDbToml and feed it to the already-parity-correct image resolver. Unit tests added. review: PR #5586 threads (honor --network-id; read edge_runtime.deno_version)
Go derives the local DB host from utils.Config.Hostname / GetHostname() (SUPABASE_SERVICES_HOSTNAME -> tcp DOCKER_HOST -> 127.0.0.1, apps/cli-go/internal/utils/misc.go:298-312), not a hardcoded loopback. The declarative generate and sync handlers hardcoded 127.0.0.1, so the edge-runtime container connected to the wrong host in dev-container / remote-Docker setups. Reuse the already-ported legacyGetHostname() (same resolver gen types / the db-config resolver use). review: PR #5586 thread (use the configured local database hostname)
…Script Replace the Go-proxy stubs for `db dump`, `db query`, and `db schema declarative generate`/`sync` with native Effect handlers in the legacy shell, plus the shared infrastructure they require (raw Postgres connection layer with COPY/queryRaw, Docker run-capture, pg-delta SSL + URL helpers, edge-runtime script layer, declarative orchestration/cache/debug-bundle, and the `__catalog` Go seam they delegate to). Output-flag parity: - `db query` honors `-o json|table|csv` (Go's command-local enum). The shared global `LegacyOutputFlag` cannot vary its choice per command (the Effect CLI builds one tree-wide global registry), so its choice is the union of every command's `--output` values and each command re-validates against its own Go enum via `withLegacyCommandInstrumentation`'s `outputFormats`, rejecting out-of-enum values with Go's byte-exact pflag message before the handler runs. - Resource commands keep rejecting `table`/`csv`; only `db query` accepts them. Connection errors: establishing the shared raw client now raises `LegacyDbConnectError` (surfaced verbatim by `copyToCsv`/`queryRaw`) instead of a misleading copy/exec error.
Mirror Go's ensureProjectGroupsCached PersistentPostRun
(apps/cli-go/cmd/root.go:176,214-234): on the --linked path the handler
now issues GET /v1/projects/{ref} and writes supabase/.temp/linked-project.json
after the query runs (success or failure), via LegacyLinkedProjectCache.
The cache layer no-ops when the file exists, the token is missing, or the
GET is non-200, so an auth-failing query still fires the GET but writes
nothing. --local / --db-url never resolve a ref and so never trigger it.
Fixes the failing e2e parity tests 'db query --linked SELECT 1' and
'[NON_AUTH]' (ci: Run end-to-end tests).
Reproduce cobra's MarkFlagsMutuallyExclusive ValidateFlagGroups errors, byte-for-byte, before any side effects run (cobra validates flag groups before PreRunE/RunE): - db query: --db-url/--linked/--local (apps/cli-go/cmd/db.go:526). Previously the handler silently branched on --linked, risking a query against a different database than intended. - db schema declarative generate: --db-url/--linked/--local (apps/cli-go/cmd/db_schema_declarative.go:499). Previously an arbitrary precedence ternary picked --local and ignored the others. - db schema declarative sync: --apply/--no-apply (apps/cli-go/cmd/db_schema_declarative.go:490). Previously --no-apply won silently in the apply-decision helper. Matches the existing inline pattern in inspect db / test db / db dump. review: PR #5586 threads (query/generate/sync mutually-exclusive targets)
The success PostRun line (Go's fmt.Println → stdout, apps/cli-go/cmd/db_schema_declarative.go:93) was written via output.raw unconditionally, so under --output-format json/stream-json it corrupted the machine payload on stdout. Gate it on output.format: keep the raw line on stdout in text mode (Go parity), emit a structured output.success in json/stream-json modes (CLI-1546: machine stdout is payload-only). Mirrors the backups/list gating pattern. review: PR #5586 thread (avoid raw stdout in JSON output mode)
Go's ToPostgresURL appends every pgconn RuntimeParams entry to the query string (apps/cli-go/internal/utils/connect.go:30-33). The TS builder dropped them, emitting only connect_timeout, so the Supavisor pooler tenant-routing `options=reference=<ref>` never reached pg-delta and declarative generate could connect to the wrong tenant on pooler fallback. Append `options` using a Go url.QueryEscape-faithful encoder (space -> +). review: PR #5586 thread (preserve pooler startup options in pg-delta URLs)
…ails When applying the generated migration fails, the user accepts reset, and `db reset --local` also fails, Go returns resetErr — the failure that blocked recovery — not the original apply error (apps/cli-go/cmd/db_schema_declarative.go:414-423). The TS port returned the apply error, hiding the reset failure. Return a reset error and include its detail in the "Database reset also failed: …" line. review: PR #5586 thread (return the reset failure after reset also fails)
Two Go-parity fixes for the declarative pg-delta edge-runtime container: - --network-id: Go's DockerStart overrides the network mode (host included) with --network-id when set (apps/cli-go/internal/utils/docker.go:267-271). The layer hardcoded host networking, so declarative runs couldn't reach the local stack on custom networks. Resolve LegacyNetworkIdFlag and pass a named network, mirroring db dump / gen types / test db. - deno_version: Go switches the edge-runtime image to the deno1 tag when [edge_runtime].deno_version = 1 (apps/cli-go/pkg/config/config.go:999-1008). The layer hardcoded the default (2). Surface deno_version from legacyReadDbToml and feed it to the already-parity-correct image resolver. Unit tests added. review: PR #5586 threads (honor --network-id; read edge_runtime.deno_version)
Go derives the local DB host from utils.Config.Hostname / GetHostname() (SUPABASE_SERVICES_HOSTNAME -> tcp DOCKER_HOST -> 127.0.0.1, apps/cli-go/internal/utils/misc.go:298-312), not a hardcoded loopback. The declarative generate and sync handlers hardcoded 127.0.0.1, so the edge-runtime container connected to the wrong host in dev-container / remote-Docker setups. Reuse the already-ported legacyGetHostname() (same resolver gen types / the db-config resolver use). review: PR #5586 thread (use the configured local database hostname)
65bf5ff to
e86f08c
Compare
The db dump --local parity test diverged because Go streams Docker image-pull progress to stderr via the Docker API + jsonmessage (apps/cli-go/internal/utils/docker.go:206-214), while the native ts-legacy LegacyDockerRun shells out to docker run (different auto-pull format). Pull progress is non-deterministic (layer IDs/order/timing) and only appears on a cache miss; Go's own dump tests mock Docker and never assert on it. Strip both pull formats (API jsonmessage + docker-run CLI) from the shared normalize() so a cold image pull no longer produces false parity failures. The CLI output itself already matches Go byte-for-byte (the schemas line, pg_dump error, 'error running container: exit N', and the --debug suggestion all have exact Go equivalents) — only the pull-progress noise differed.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cba59d44cd
ℹ️ 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".
Go's confirmOverwrite goes through Console.PromptYesNo, which returns true immediately when the global YES flag is set (apps/cli-go/internal/utils/console.go:70-73). The TS handler always prompted, so --yes runs errored in non-interactive/JSON mode and blocked in a TTY. Gate the overwrite prompt on the yes flag. review: PR #5586 thread (honor --yes for overwrite prompts)
…ations Go's applyMigrationToLocal connects with utils.Config.Hostname (apps/cli-go/cmd/db_schema_declarative.go:463); the apply path still hardcoded 127.0.0.1 even after the diff/generate URLs were switched to legacyGetHostname(). Under SUPABASE_SERVICES_HOSTNAME / tcp DOCKER_HOST, --apply wrote the migration then applied it to the wrong host. Use legacyGetHostname() for the apply too. review: PR #5586 thread (use configured hostname when applying sync migrations)
The hidden supabase-go __catalog seam provisions the shadow DB via DockerStart, which reads --network-id from viper (apps/cli-go/internal/utils/docker.go:267-271). The seam argv omitted it, so on a custom network the shadow/catalog containers landed on the default network while pg-delta containers used --network-id. Forward --network-id on the seam argv when set, like LegacyGoProxy does. review: PR #5586 thread (forward --network-id into the catalog seam)
Two Go-parity fixes for the native db dump: - --schema/--exclude are cobra StringSlice in Go (apps/cli-go/cmd/db.go:432,444), which comma-splits each value before building the pg_dump env. The Effect CLI flags don't split, so --schema public,auth emitted one pattern; split on comma. - Go chdir's into the workdir before opening --file (cmd/root.go:104), so a relative --file resolves against the workdir. Resolve flags.file against cliConfig.workdir for the open, write, and the reported absolute path. review: PR #5586 threads (split dump comma-list flags; resolve dump output files from the workdir)
Go chdir's into the workdir in PersistentPreRunE before ResolveSQL reads --file (apps/cli-go/cmd/root.go:104), so a relative --file resolves against the workdir, not the original process cwd. Resolve flags.file against cliConfig.workdir. review: PR #5586 thread (resolve db query files from the workdir)
Go's db query --linked PreRun calls flags.LoadProjectRef (load-or-fail, no prompt) and errors with ErrNotLinked when the workdir isn't linked (apps/cli-go/cmd/db.go). The TS handler used the prompting resolve(), which on an interactive TTY would prompt for a project and run the SQL against it. Use the non-prompting resolveOptional, failing with the not-linked error and validating the ref like Go's AssertProjectRefIsValid. review: PR #5586 thread (reject unlinked --linked query instead of prompting)
…ve generate Go's smart-mode 'Custom database URL' branch parses the entry with pgconn.ParseConfig and feeds pg-delta a normalized ToPostgresURL (apps/cli-go/cmd/db_schema_declarative.go:283-287). The TS branch only empty-checked and returned the raw string, so malformed URLs were passed to pg-delta and valid libpq DSNs were not normalized. Parse with parseLegacyConnectionString (project-env layered, like the --db-url path), fail with the redacted 'failed to parse connection string' error, and return legacyToPostgresURL on success. review: PR #5586 thread (validate smart custom database URLs before pg-delta)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e37dba771
ℹ️ 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".
…E_EFFECTS (ci: code quality)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 842dfd6170
ℹ️ 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".
| // Validate the `-o` enum first, before instrumentation runs the handler, so a | ||
| // rejected flag fails without emitting a `cli_command_executed` event — Go | ||
| // rejects it at parse time, before telemetry. | ||
| Effect.andThen(validateLegacyOutputFormat(allowed), instrument(self)); |
There was a problem hiding this comment.
Validate -o before acquiring command layers
Because this per-command enum check runs inside the handler effect, it only executes after the command's Command.provide(...) layer has been acquired. Now that the global parser accepts table/csv, an eager Management API command such as projects list can build legacyManagementApiRuntimeLayer and resolve the access token before this validation runs; in a logged-out shell, supabase projects list -o table will fail with the missing-token error instead of Go's parse-time invalid argument "table"..., and logged-in runs still perform auth/keyring work before rejecting the flag. The output enum rejection needs to happen before command runtime acquisition, or the parser needs a pre-runtime per-command rejection path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed — this is a real parity gap, not just theoretical. In Go the global -o/--output is an EnumFlag allowing only [env|pretty|json|toml|yaml] (cmd/root.go:332, internal/utils/enum.go:20-26), and pflag rejects an out-of-set value during flag parsing, before PersistentPreRunE/auth runs (cmd/root.go:93,159). So supabase projects list -o table fails at parse time with invalid argument "table" for "-o, --output" flag: ... and never touches auth/keyring.
Here the union global flag (shared/legacy/global-flags.ts, widened to include table/csv for db query) pushes per-command rejection into validateLegacyOutputFormat inside the handler effect (legacy-command-instrumentation.ts), which runs after Command.provide(...) builds the runtime. For eager Management API commands (projects list), legacyPlatformApiLayer resolves+validates the access token at layer-build time, so logged-out runs fail with the missing-token error and logged-in runs do keyring/auth work before the flag is rejected. (db query avoids this because it uses the lazy resolver runtime.)
I don't think there's a safe drive-by fix, so I'm flagging this for a maintainer decision rather than forcing it. The Effect CLI builds one tree-wide global-flag registry, so we can't restore Go's per-command parse-time Flag.choice (the constraint documented at global-flags.ts:3-11). The two viable fixes both have non-trivial blast radius:
- Lift the enum check outside
Command.providefor the ~72 commands that usewithLegacyCommandInstrumentation(split validation out of the instrumentation wrapper). Most parity-faithful, since the check only needsLegacyOutputFlag(a root global) — but it changes the command-wiring contract broadly. - Make the eager Management API runtime lazy (like
legacyLinkedDbResolverRuntimeLayer) so no token resolves at build — but that shifts auth-failure ordering/timing for every eager command.
Both interact with telemetry ordering and the CLI-1546 quiet-layer wiring, so I'd rather not pick one inline. @author — which approach do you prefer? I'd lean toward (1). Leaving this open for your call.
…Token to match Go LoadAccessTokenFS (review: #PRRT_kwDOErm0O86KUyNX)
There was a problem hiding this comment.
💡 Codex Review
When a linked DB command has a [remotes.<ref>] block that overrides a base config value which would otherwise be invalid, this unmerged read fails before the ref-specific read below can apply the override. Go's linked ParseDatabaseConfig loads the project ref first and then LoadConfig merges [remotes.<ref>] before decoding/validating, so a base db.major_version = 12 corrected to 17 in the matching remote block succeeds there but db dump/query --linked fails here before reaching the merged validation.
ℹ️ 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".
|
CI status: the 3 red checks are infra flakes, not this PR. 🟡 The failing checks —
All unit, integration, type, lint, fmt and knip checks pass, and every non-e2e CI check is green. I re-ran the failed jobs once; they hit the same sustained registry throttling, so I'm not re-running blindly again. This needs an infra/maintainer action rather than a code change here — e.g. authenticating the e2e Docker pulls, routing the I'll leave the reruns to a maintainer to avoid burning the rate-limit budget. Happy to re-run once the registry throttling clears. |
…match Go flags.Visit (review: #PRRT_kwDOErm0O86KVYMQ #PRRT_kwDOErm0O86KVYMS)
…ses (ci: code quality)
…surface, matching Go (review: #PRRT_kwDOErm0O86KVYMH)
… to match config.Load (review: #PRRT_kwDOErm0O86KVYMN)
…ump to match Go (review: #PRRT_kwDOErm0O86KVYMW)
|
CI status: the The The green spec's Crucially, unlike the earlier This is independent of this PR — the diff here is entirely TypeScript under |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f8df2b0fa7
ℹ️ 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".
…to match Go (review: #PRRT_kwDOErm0O86KWEZP #PRRT_kwDOErm0O86KWEZT)
…to match Go (review: #PRRT_kwDOErm0O86KWEZQ #PRRT_kwDOErm0O86KWEZX)
…s surface, matching Go (review: #PRRT_kwDOErm0O86KWEZk)
…o match Go (review: #PRRT_kwDOErm0O86KWEZa)
…unctionSlug (review: #PRRT_kwDOErm0O86KWEZf)
…uery-schema-commands
jgoux
left a comment
There was a problem hiding this comment.
I found one small docs drift issue in the current head.
…pped handler (review: #PRRT_kwDOErm0O86Ke6RP)
…upabase#5620) Ports `supabase db diff` and `supabase db pull` from the Go CLI to native Effect handlers in the legacy shell, replacing their Phase-0 proxy stubs. Linear: CLI-1313. Builds on the pg-delta infrastructure merged in supabase#5586. ## What changed **Shared engine (promotion + new modules)** - Promotes the pg-delta engine — adapter, seam, cache, deno-templates, declarative write, and its errors — out of `db/schema/declarative/` into `db/shared/legacy-pgdelta.*`, so `diff`, `pull`, and `declarative` import one copy. The `generate`/`sync` call sites are updated in the same change; `findDropStatements` is hoisted to `legacy-sql-split.ts`. - New shared modules: `legacy-diff-engine.ts` (pure `resolveDiffEngine` / `resolvePullDiffEngine` / `shouldUsePgDelta`), `legacy-migra.*` (byte-exact Go templates + a `legacyDiffMigra` free function over the pg-delta context with the OOM→`supabase/migra` bash fallback), and migration-file helpers. **Live-shadow Go seam** - Adds a hidden `db __shadow` command (with `PrepareShadowSource` / `PrepareRawShadow`, refactored out of `DiffDatabase` / `pullDeclarativePgDelta`). It provisions the shadow Postgres, prints its connection URL + container id, and leaves it running so the native TS handler runs migra/pg-delta against it and tears it down — mirroring Go's `differ(shadowConfig, config)` for both engines (migra needs a live source, which the catalog-file `__catalog` seam couldn't provide). `start.SetupDatabase` stays unported, which is why provisioning remains in Go. **db diff** - Native pg-delta / migra diff via edge-runtime; explicit `--from`/`--to` mode (always pg-delta). `--use-pgadmin` / `--use-pg-schema` rebuild the argv and delegate to the bundled Go binary (telemetry disabled on the child so the single `cli_command_executed` event comes from the TS command). **db pull** - Native pg-delta / migra migration pull + `--declarative` pg-delta export; reconciles `supabase_migrations.schema_migrations` and updates remote history on confirmation. `--use-pg-delta` is hidden + emits the cobra deprecation line. The `--experimental` structured dump and initial-pull `pg_dump` (migra) sub-branches delegate to the Go binary (unported `pg_dump`). **Cross-cutting** - Adds `--output-format json`/`stream-json` envelopes (text reproduces Go bytes); unit + integration + e2e tests; `SIDE_EFFECTS.md` for both; flips both commands to `ported` in `go-cli-porting-status.md`. ## Reviewer notes - **Architecture decision (vs the stale CLI-1313 issue text):** pg-delta/migra run via the edge-runtime Deno container, not a library import — Docker is intentionally retained for byte-exact Go parity and zero new published deps, following supabase#5586. - **Parity gap to flag:** an empty `db pull` exits **non-zero** (`No schema changes found`), matching Go's `errInSync`; `db diff` exits 0. Documented in `pull/SIDE_EFFECTS.md`. - **Deliberately preserved Go behaviors:** the migra bash-fallback shell construction and `--file`/`--output`/migration-name path handling reproduce pre-existing Go behavior rather than diverging from the strict 1:1 port. - The migra OOM bash fallback and the linked-pull IPv4 pooler fallback for declarative export are the thinnest-tested paths.
What changed
Replaces the Go-proxy stubs for
db dump,db query, anddb schema declarative generate/syncwith native Effect handlers in the legacy shell, along with the shared infrastructure they need:legacy-db-connection.sql-pg.layer.ts): rawpgclient for the COPY protocol and full-metadataqueryRaw(command tag via thecommandCompleteprotocol message), reusing the winning dial target so TLS/fallback/DoH parity holds.__catalogGo seam (apps/cli-go/...) the TS port delegates to for shadow-database provisioning.Why / reviewer context
Strict Go parity is the contract for the legacy shell. Behaviors that look improvable but match Go are intentional and documented in each
SIDE_EFFECTS.md(e.g.db dump --dry-runprints the resolvedPGPASSWORDin cleartext like Go'snoExec;db query --linkednon-2xx maps to a uniformunexpected statusmessage; failed declarativesync --applyleaves the migration file on disk).-o/--outputparity. Go registers--outputper command (db query→json|table|csv; resource commands →env|pretty|json|toml|yaml). The Effect CLI hoists global flags into a single tree-wide registry, so a command cannot redeclare anoutputglobal to vary its enum. The sharedLegacyOutputFlagchoice is therefore the union of all commands' values, and each command re-validates against its own Go enum inwithLegacyCommandInstrumentation(outputFormats), rejecting out-of-enum values with Go's byte-exact pflag message (invalid argument "x" for "-o, --output" flag: must be one of [ … ]) before the handler runs and before any telemetry event fires. The validation reads the flag viaEffect.serviceOption, so it adds no requirement to the wrapper. Net result:db query -o csv/tableworks; resource commands still rejecttable/csvexactly as Go does. This change is fully legacy-scoped —next/uses its own--output-formatflag and is untouched.Connection error typing. Establishing the shared raw client now raises
LegacyDbConnectError(surfaced verbatim by bothcopyToCsvandqueryRaw) rather than a misleading "failed to copy output" / "failed to execute query".Follow-ups (tracked, not in scope)
db dump --linkedIPv6 suggestion uses the genericipv6Suggestion()text on the no-fallback / failed-retry path rather than Go'sSuggestIPv6Pooler, which prefills the project's specific pooler connection string. Surfacing that exact URL needs the pooler string exposed at this seam — noted indump/SIDE_EFFECTS.md. (The container-level pooler fallback retry itself is ported, and dump output streams to--file.)CLOSES CLI-1315