Skip to content

Bootstrap the is_admin() stub before anything references it - #166

Merged
YurMil merged 1 commit into
mainfrom
fix/is-admin-bootstrap-order
Aug 16, 2026
Merged

Bootstrap the is_admin() stub before anything references it#166
YurMil merged 1 commit into
mainfrom
fix/is-admin-bootstrap-order

Conversation

@YurMil

@YurMil YurMil commented Aug 16, 2026

Copy link
Copy Markdown
Owner

What was broken

The Supabase Preview check has been failing since July with:

ERROR: function public.is_admin() does not exist (SQLSTATE 42883)
At statement: 5
create policy "Admins can read audit log"
  on public.admin_audit_log
  for select
  to authenticated
  using (public.is_admin())

is_admin() was defined straight in the production database and never entered this migration history, so a database built from these files alone does not have it. 20260720000000 already creates a deny-all stub — three weeks after the first migration that needs one.

Because the check only runs when supabase/ changes, it fails on exactly the PRs where it matters and is silently skipped everywhere else. No migration has been validated by CI since 2 July, including the one merged in #164 an hour ago.

The fix

The stub moves to 20260613000001, the first migration that mentions is_admin() at all.

That is deliberately not the first migration that breaks. A reference inside a function body resolves when the function runs; a policy's USING expression resolves as the policy is created. So 20260613000001 applied cleanly and 20260702000000 was the one that died. Guarding the earliest mention covers every later reference instead of only the first one that happened to be a policy.

The block in 20260720000000 stays — it is part of applied history and no-ops once the earlier one has run. Only its comment changes, to say where the stub now comes from.

Editing applied migrations is safe here. Supabase records them by version, so production re-runs neither file; and production's real is_admin() is untouched in any case, because the guard creates the stub only when the function is absent.

Verification

On a fresh Postgres 17 with no is_admin() present, scaffolded with only what Supabase itself provides (auth.users, auth.uid(), public.profiles):

  • all 15 migrations apply in order, zero errors — previously the run died at 20260702000000;
  • re-applying both guard-bearing migrations over a real is_admin() that returns true leaves it returning true, so the stub cannot clobber production;
  • both re-apply idempotently (notices only).

Final state after the in-order replay is the intended one:

assertion result
anon/authenticated grants on client_error_log authenticated: SELECT only
anon column-level INSERT grants 0
INSERT policies on client_error_log 0
record_client_error present yes
client_error_report_quota present yes

Follow-up worth considering

The scaffolding above — auth.uid(), auth.users, profiles — is what a from-scratch database still cannot produce on its own. profiles in particular is referenced by 20260702000003 and defined nowhere in this history, so the same class of failure is one policy away from returning. Bringing those into the migration history would make the preview check trustworthy rather than merely passing.

🤖 Generated with Claude Code

Preview branches have been failing since July with "function public.is_admin()
does not exist", raised by the audit-log policy in 20260702000000. The function
was defined straight in the production database and never entered this
migration history, so a database built from these files alone does not have it.
20260720000000 already creates a deny-all stub, but that is three weeks too
late to help.

The stub moves to 20260613000001, the first migration that mentions is_admin()
at all. That is deliberately not the first migration that breaks: a reference
inside a function body resolves when the function runs, while a policy's USING
expression resolves as the policy is created, so 20260613000001 applied
cleanly and 20260702000000 was the one that failed. Guarding the earliest
mention covers every later reference rather than only the first that happened
to be a policy.

The block in 20260720000000 stays — it is part of applied history, and it
no-ops once the earlier one has run. Its comment is updated to say where the
stub now comes from.

Editing applied migrations is safe here: Supabase records them by version, so
production will not re-run either file, and production's real is_admin() is
untouched regardless — the guard creates the stub only when the function is
absent.

Verified on a fresh Postgres 17 with no is_admin() present, only the pieces
Supabase itself provides (auth.uid, auth.users, profiles): all 15 migrations
apply in order with no errors, where before the run died on 20260702000000.
Re-applying both guard-bearing migrations over a real is_admin() that returns
true leaves it returning true, so the stub cannot clobber production. Final
state after the in-order replay is the intended one: anon holds no table or
column INSERT on client_error_log, no INSERT policy remains, and
record_client_error plus the quota table are present.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cadautoscript-com Ready Ready Preview Aug 16, 2026 11:16am

@supabase

supabase Bot commented Aug 16, 2026

Copy link
Copy Markdown

Updates to Preview Branch (fix/is-admin-bootstrap-order) ↗︎

Deployments Status Updated
Database Sun, 16 Aug 2026 11:11:35 UTC
Services Sun, 16 Aug 2026 11:11:35 UTC
APIs Sun, 16 Aug 2026 11:11:35 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Sun, 16 Aug 2026 11:11:41 UTC
Migrations Sun, 16 Aug 2026 11:11:43 UTC
Seeding ⏸️ Sun, 16 Aug 2026 11:11:20 UTC
Edge Functions ⏸️ Sun, 16 Aug 2026 11:11:20 UTC

❌ Branch Error • Sun, 16 Aug 2026 11:11:43 UTC

ERROR: relation "public.profiles" does not exist (SQLSTATE 42P01)
At statement: 2
drop trigger if exists trg_log_role_change on public.profiles

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@YurMil
YurMil merged commit 6634a77 into main Aug 16, 2026
7 of 8 checks passed
YurMil added a commit that referenced this pull request Aug 17, 2026
The Supabase check reports two different failures depending on where it runs,
and the runbook only covered one of them. On a pull request it builds a database
from the migration files and dies on public.profiles. On main it syncs to
production and dies on "Remote migration versions not found in local migrations
directory" — which it has been doing since at least 7 August, before any of the
recent work.

That second failure matters for task 1: nothing has synced to production for
weeks, so db push is not going to apply the migration cleanly the way the
document originally implied. Added the diagnosis, the migration list step, and
the choice between capturing a remote-only version and discarding the record —
with a warning against reaching for migration repair to silence the error, since
that is how the drift got this far.

Also frames the whole document around that drift: the repository and the
production database disagree in both directions, and every task here is a
symptom of it. Status table refreshed — #165 and #166 are merged, nanoid opened
a new high alert today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
YurMil added a commit that referenced this pull request Aug 17, 2026
Three things are pending that a checkout cannot do on its own, and one of them
is time-sensitive: PR #164 is merged and deployed, so the production bundle
already calls an Edge Function that does not exist yet. Error reports are being
dropped, and the anon INSERT grant the PR exists to remove is still in place
until the migration is applied.

Also records the second half of the preview-check story. PR #166 fixes the
is_admin() ordering; the check then fails on public.profiles, which is
referenced by five migrations and defined by none. Closing that needs the real
table definition dumped from production — reconstructing it from the columns
the migrations happen to touch would turn the check green while describing a
schema that does not exist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
YurMil added a commit that referenced this pull request Aug 17, 2026
The Supabase check reports two different failures depending on where it runs,
and the runbook only covered one of them. On a pull request it builds a database
from the migration files and dies on public.profiles. On main it syncs to
production and dies on "Remote migration versions not found in local migrations
directory" — which it has been doing since at least 7 August, before any of the
recent work.

That second failure matters for task 1: nothing has synced to production for
weeks, so db push is not going to apply the migration cleanly the way the
document originally implied. Added the diagnosis, the migration list step, and
the choice between capturing a remote-only version and discarding the record —
with a warning against reaching for migration repair to silence the error, since
that is how the drift got this far.

Also frames the whole document around that drift: the repository and the
production database disagree in both directions, and every task here is a
symptom of it. Status table refreshed — #165 and #166 are merged, nanoid opened
a new high alert today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant