Skip to content

perf(cli): reuse shadow baseline cache in migration squash and pgadmin diff - #6220

Open
avallete wants to merge 6 commits into
claude/shadow-db-parallel-provision-fn9uuefrom
avallete/shadow-cache-squash-pgadmin
Open

perf(cli): reuse shadow baseline cache in migration squash and pgadmin diff#6220
avallete wants to merge 6 commits into
claude/shadow-db-parallel-provision-fn9uuefrom
avallete/shadow-cache-squash-pgadmin

Conversation

@avallete

Copy link
Copy Markdown
Member

Summary

Stacked on #6215 (← #6203#6184#6102).

migration squash and db diff --use-pgadmin were the last two shadow-database consumers still provisioning bare cold shadows — every run re-paid the full platform baseline (init schema + the PG15+ realtime/storage/auth one-shot migrate jobs, ~15s) that the shadow-baseline cache already eliminates for db diff/db pull. Both now provision through legacyWithShadowDatabase and warm-restore the cached pg_data tar when eligible.

The shared prologue (connect → prelude → legacySetupDatabase, with the warm/cold/cold-snapshotting branches) is extracted into legacyOpenShadowBaselineSession in shadow-database.ts; legacySetupShadowDatabase and the migrate path delegate to it, and squash calls it directly so its pre-migration auth/storage dumps stay exactly at the baseline seam. Squash keys config-following (its baseline always followed config.toml); pgadmin keys webhooks: "enabled" to share the legacy migra branch's forced-pg_net tars.

Two defects surfaced while wiring this in, both fixed here:

  • Cache-key/cluster-state mismatch (correctness): db pull and db diff's native branch hardcoded { webhooks: "enabled" } as the cache-key opts even in pg-delta next mode, where the actual provisioning (legacyMigrateNextShadowDatabase) follows config.toml. On a project with webhooks disabled, a cold next-mode run published a pg_net-less tar under the "enabled" key — which the newly cached pgadmin branch (and the reverse direction) could then silently warm-restore. The key opts now follow the migration mode at all call sites; legacyResolveMigrationsCatalogRef in legacy-pgdelta.cache.ts had the same drift (keyed config-following, provisioned forced-on) and is fixed too. A regression test asserts a next-mode diff and a pgadmin diff on a webhooks-disabled config never share tars (verified failing pre-fix).
  • Readiness gate (perf): both call sites gated on legacyWaitForHealthyServices (Docker HEALTHCHECK, first probe at t+10s, ~6.5s pure wait) instead of the legacyWaitForShadowReady connect probe every other cached consumer uses. Swapped, so the warm restore actually lands its win. The now-dead neverHealthy mock knob is deleted; the interrupt/timeout tests model a connect-refusing shadow instead.

Test-helper cleanups ride along: a shared withLegacyShadowCacheEnabled in tests/helpers/legacy-mocks.ts replaces per-file env stash/restore copies, and the squash test uses legacyShadowBaselineCacheDir instead of a hand-built path.

Transcript note: on a warm hit both commands skip the Initialising schema... / Seeding globals from roles.sql... lines, matching the existing warm behavior of db diff/db pull. All dumps, migration-replay lines, and result output are unchanged.

Linked issue

Closes #

  • The linked issue is open and carries the open-for-contribution label (or I'm a Supabase maintainer).

Checklist

  • The PR title follows Conventional Commits (e.g. fix(cli): …).
  • Tests added or updated for the change.
  • pnpm check:all and pnpm test pass for the workspace(s) I touched.

🤖 Generated with Claude Code

jgoux and others added 5 commits August 15, 2026 21:13
## Summary

- settle chained managed identity recovery from the latest discovery
state
- keep branch-scoped transitions isolated while preserving conflict
evidence
- preserve marker, Git config, and service error semantics across
recovery races

## Context

This follows PR #6202 and Linear CLI-2108. It addresses confirmed
pre-wiring edge cases around interrupted branch recovery,
rename-plus-move settlement, folder-to-Git marker reporting, duplicated
Git config values, and service shutdown races.

Automatic recovery remains internal and bounded. Explicit recovery
operations and fail-closed ambiguity behavior are unchanged.
## TL;DR

fixes `supabase test db` reporting success on a run that executed zero
tests,
which was caused by passing `pg_prove`'s exit code straight through when
it prints `Result: NOTESTS` and still exits 0
and is now fixed by watching the TAP summary for that verdict and
failing with a msg naming the paths that were searched.
A typo'd path, an empty tests directory, and a bind mount the Docker
daemon resolved against a different filesystem than the CLI all hit
this, so CI could stay green while testing nothing.
The TAP stream on stdout is unchanged, the diagnostic goes to stderr,
and the new exit 1 is recorded in the Go divergence because Go exits 0
here...

## ref:
- closes: #6206
## TL;DR

In text mode the CLI wrote to stdout with `process.stdout.write` and
ignored its return value, so it never waited for a slow reader.
Piping a large payload into a slower consumer made Node queue the whole
thing in memory, which `supabase db dump` can turn into an OOM on a big
database.

Writing through the `Stdio` sink is better than hand-checking that
return value because the sink already waits for `drain` and handles
stream errors, the json and stream-json layers were always doing exactly
that, and the
 old Go CLI had the same behaviour from blocking `os.Stdout` syscall.
Text mode was the only outlier, so this removes the inconsistency
instead of adding another special case, and all three layers now share
one writer. Output bytes and ordering are unchanged....

## ref: 
- spotted in: #6210
## Summary

- centralize workspace metadata derivation and first-start settlement
policy in focused pure modules
- remove the obsolete Promise path canonicalizer and share the Git lock
retry schedule
- strengthen deterministic recovery-race coverage, close a SQLite test
handle leak, and correct architecture documentation

This is a maintainability follow-up to #6214. It deliberately keeps the
broader `packages/stack` decomposition out of scope so that work can be
designed separately.
…n diff

The two remaining shadow-database consumers still provisioned bare, uncached
shadows. Both now acquire through `legacyWithShadowDatabase` (`shadow-cache.ts`),
the same seam `db diff`'s native branch and `db pull`'s migration path already
use, so a key-matching PGDATA snapshot is restored in a few seconds instead of
cold-provisioning the platform baseline every run.

`migration squash` keeps its dump/apply/dump sequence intact by resuming at the
baseline seam: `legacyOpenShadowBaselineSession` — Go's
`SetupShadowDatabase`/`MigrateShadowDatabase` shared prologue, extracted from
`shadow-database.ts` so all three compositions share one implementation — hands
back the still-open session, and squash's before-dump / apply-migrations /
after-dump / full-dump steps are unchanged. Unlike diff/pull it passes no
`webhooks` override, matching its long-standing config-following
`SetupDatabase` call, so it keys to its own snapshots.

`db diff --use-pgadmin` becomes structurally identical to the native branch:
same cached acquire, same forced-on `webhooks: "enabled"` policy (so it shares
the native branch's tars), with `legacyMigrateShadowDatabase` now told whether
the cluster already carries the baseline.

A warm shadow skips `SetupDatabase`, so `Initialising schema...` /
`Seeding globals from roles.sql...` no longer print and the PG15+ one-shot
migrate jobs do not run — progress text reflects the work actually performed.
Everything else, including the dumped artifacts and the differ output, is
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@avallete
avallete requested a review from a team as a code owner August 16, 2026 11:20
@avallete
avallete changed the base branch from claude/shadow-db-parallel-provision-fn9uue to develop August 16, 2026 12:26
@avallete
avallete changed the base branch from develop to claude/shadow-db-parallel-provision-fn9uue August 16, 2026 12:28
@github-actions

Copy link
Copy Markdown
Contributor

Supabase CLI preview

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

Preview package for commit d6289e6.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

describeLive("shadow baseline cache (live Docker)", () => {

P1 Badge Gate the shadow-cache live suite on Docker availability

When SUPABASE_ACCESS_TOKEN is configured on a developer machine or runner without a reachable Docker daemon, describeLive activates this suite and the first container operation fails instead of skipping it. This scenario directly requires Docker, so use describeDockerLive, which performs the required docker info gate.

AGENTS.md reference: apps/cli/AGENTS.md:L524-L526


// Same policy as `legacyWaitForHealthyServices` (Go's
// `NewBackoffPolicy(ctx, timeout)`): a 1-second constant delay, capped at
// `timeoutSeconds` retries after the initial attempt.
const schedule = Schedule.max([Schedule.spaced("1 seconds"), Schedule.recurs(timeoutSeconds)]);

P2 Badge Bound shadow readiness by elapsed timeout

When the published port blackholes TCP connections rather than refusing them, each probe can consume its full 2-second connect timeout, while this schedule still permits timeoutSeconds retries with one-second delays. The default 30-second health timeout can therefore take roughly 90 seconds before failing, changing the established shadow Docker behavior for every migrated caller; apply an elapsed-time deadline to the whole retry loop rather than treating the timeout as only a retry count.

AGENTS.md reference: apps/cli/AGENTS.md:L53-L56



P2 Badge Track the current migration before its write can fail

When writeFileString creates the exclusive file but then fails while writing it, such as on a full disk or an interrupted filesystem write, this push is never reached. The error cleanup consequently removes previously completed files but leaves the current partial .sql file in supabase/migrations, where a later command can treat it as a real migration or collide with its version; include the current path in cleanup as soon as creation begins.


process.env.SUPABASE_HOME = supabaseHome;

P2 Badge Restore shadow-cache environment after the live test

When another live test runs later in the same Vitest worker, this direct process.env mutation remains after the scoped temporary directory has been deleted; the test also leaves SUPABASE_SHADOW_CACHE=1 set below. Subsequent tests can therefore unexpectedly enable caching against a nonexistent former home or lose the caller's original environment values. Scope both assignments with the existing withLegacyShadowCacheEnabled helper or restore them in a finalizer.

ℹ️ 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".

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