Skip to content

fix(supabase): add pgmq_public wrapper migration so local stack starts#717

Merged
softmarshmallow merged 1 commit into
mainfrom
fix/supabase-pgmq-public
May 17, 2026
Merged

fix(supabase): add pgmq_public wrapper migration so local stack starts#717
softmarshmallow merged 1 commit into
mainfrom
fix/supabase-pgmq-public

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented May 17, 2026

Summary

Local supabase start / db reset aborts with Error status 503 because PostgREST cannot load its schema cache:

{"code":"3F000","message":"schema \"pgmq_public\" does not exist"}

supabase/config.toml exposes pgmq_public to PostgREST, but no migration ever created that schema — it only existed on the hosted project via the Studio "Expose Queues via PostgREST" toggle, and on whoever-set-it-up's old local DB volume. A newer Supabase CLI / clean volume replays repo migrations only, so the always-missing schema is now exposed and blocks every fresh checkout.

This adds a forward-only migration capturing the canonical Supabase pgmq_public wrapper (schema + pop/send/read/archive/delete), mirroring what the hosted dashboard generates so local == prod.

  • Grants are service_role-only per supabase/AGENTS.md deny-by-default. The sole consumer, jobs/src/main.ts, uses the service-role key; anon/authenticated get no schema USAGE.
  • Idempotent (create schema if not exists, create or replace function) and forward-only.
  • Ships a pgTAP suite proving the boundary (schema/functions exist, service_role allowed, anon/authenticated denied).

References

Known upstream Supabase gap: pgmq_public is created by the Studio "Expose Queues via PostgREST" toggle, not by migrations, so it is absent on clean local / self-hosted databases.

⚠️ Production rollout note

Prod already has these objects (created by the dashboard). Do not let CI run this migration's SQL on prod — create or replace function would overwrite the dashboard-generated definitions and hard-fail on any return-type drift. Instead reconcile history only:

supabase migration repair --status applied 20260517144641

This records the version as applied without executing the SQL, closing the drift while leaving prod's working pgmq_public untouched.

Test plan

  • supabase stop && supabase start — full clean migration replay succeeds; stack healthy
  • PostgREST logs: Schema cache loaded with zero pgmq_public does not exist errors
  • supabase db test — PASS, 146/146 across 8 suites incl. new pgmq_public_grants_test (no regressions)
  • On merge: run the migration repair above against prod (do NOT db push the SQL)

config.toml exposes pgmq_public to PostgREST, but no migration ever
created it — it only existed via the hosted Studio "Expose Queues via
PostgREST" toggle. A clean `supabase db reset` / newer CLI therefore
left PostgREST unable to load its schema cache (schema "pgmq_public"
does not exist), aborting the local stack.

Capture the canonical Supabase wrapper (schema + pop/send/read/archive/
delete) as a forward-only migration so any fresh checkout works.
service_role-only grants per supabase/AGENTS.md deny-by-default; the
sole consumer (jobs/src/main.ts) uses the service-role key. Ships a
pgTAP test proving anon/authenticated are denied.

Prod already has these objects from the dashboard; reconcile prod
history with `supabase migration repair --status applied 20260517144641`
(do NOT run the SQL on prod).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 17, 2026

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

7 Skipped Deployments
Project Deployment Actions Updated (UTC)
backgrounds Ignored Ignored Preview May 17, 2026 3:06pm
blog Ignored Ignored Preview May 17, 2026 3:06pm
code Ignored Ignored May 17, 2026 3:06pm
docs Ignored Ignored Preview May 17, 2026 3:06pm
grida Ignored Ignored Preview May 17, 2026 3:06pm
legacy Ignored Ignored May 17, 2026 3:06pm
viewer Ignored Ignored Preview May 17, 2026 3:06pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Walkthrough

This PR introduces a new pgmq_public schema with five wrapper functions that safely expose the underlying pgmq message queue operations through PostgREST. The migration creates the schema, defines wrappers (pop, send, read, archive, delete) with restricted search paths, and grants service_role the necessary permissions while maintaining deny-by-default for other roles. Accompanying tests verify the schema structure, function signatures, and permission model.

Changes

pgmq_public API Wrapper with Permissions

Layer / File(s) Summary
Schema creation, wrapper functions, and permission grants
supabase/migrations/20260517144641_pgmq_public_wrapper.sql, supabase/tests/pgmq_public_grants_test.sql
Creates pgmq_public schema with five wrapper functions (pop, send, read, archive, delete) that delegate to underlying pgmq functions. Grants service_role schema USAGE and function EXECUTE permissions on wrappers; grants broad SELECT, UPDATE, DELETE on pgmq tables/sequences with default privileges for future objects. Deny-by-default policy prevents anon and authenticated from accessing the schema. pgTAP test suite validates schema existence, function signatures, expected permission grants, and role access boundaries.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • gridaco/grida#318: Realtime indexing implementation that consumes pgmq queue operations (send/read/archive) exposed by these wrapper functions.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a pgmq_public wrapper migration to fix local stack startup. It directly relates to the core problem solved in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/supabase-pgmq-public

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
supabase/tests/pgmq_public_grants_test.sql (1)

45-52: ⚡ Quick win

Expand EXECUTE privilege assertions to all granted wrappers.

You grant EXECUTE on five functions in the migration, but this suite validates only two. Add assertions for pop, send, and delete so permission regressions are caught.

Suggested test additions
 SELECT ok(
+  has_function_privilege('service_role', 'pgmq_public.pop(text)', 'EXECUTE'),
+  'service_role can EXECUTE pgmq_public.pop'
+);
+SELECT ok(
+  has_function_privilege('service_role', 'pgmq_public.send(text,jsonb,integer)', 'EXECUTE'),
+  'service_role can EXECUTE pgmq_public.send'
+);
+SELECT ok(
   has_function_privilege('service_role', 'pgmq_public.read(text,integer,integer)', 'EXECUTE'),
   'service_role can EXECUTE pgmq_public.read'
 );
 SELECT ok(
   has_function_privilege('service_role', 'pgmq_public.archive(text,bigint)', 'EXECUTE'),
   'service_role can EXECUTE pgmq_public.archive'
 );
+SELECT ok(
+  has_function_privilege('service_role', 'pgmq_public.delete(text,bigint)', 'EXECUTE'),
+  'service_role can EXECUTE pgmq_public.delete'
+);

As per coding guidelines, "Add pgTAP test coverage for every table that alters RLS, permissions, or tenant boundaries...".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/tests/pgmq_public_grants_test.sql` around lines 45 - 52, The tests
only assert EXECUTE for pgmq_public.read and pgmq_public.archive but the
migration grants EXECUTE on five wrappers; add pgTAP assertions for the
remaining functions by adding SELECT ok(has_function_privilege('service_role',
'pgmq_public.pop(<same signature as in migration>)', 'EXECUTE'), 'service_role
can EXECUTE pgmq_public.pop'), SELECT ok(has_function_privilege('service_role',
'pgmq_public.send(<same signature as migration>)', 'EXECUTE'), 'service_role can
EXECUTE pgmq_public.send'), and SELECT ok(has_function_privilege('service_role',
'pgmq_public.delete(<same signature as migration>)', 'EXECUTE'), 'service_role
can EXECUTE pgmq_public.delete') ensuring you use the exact function signatures
used in the migration for pop, send, and delete.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/migrations/20260517144641_pgmq_public_wrapper.sql`:
- Around line 17-20: Add an explicit revoke for public execute privileges on the
wrapper functions so PUBLIC cannot call them by default: after you finish
creating/granting the wrapper functions in schema pgmq_public (and similarly for
pgmq if wrappers are created there), add a statement to revoke EXECUTE from
PUBLIC, e.g. use REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM
PUBLIC to enforce deny-by-default for anon/authenticated; ensure this revoke
comes after the grants that assign service_role privileges so service_role
retains access.

---

Nitpick comments:
In `@supabase/tests/pgmq_public_grants_test.sql`:
- Around line 45-52: The tests only assert EXECUTE for pgmq_public.read and
pgmq_public.archive but the migration grants EXECUTE on five wrappers; add pgTAP
assertions for the remaining functions by adding SELECT
ok(has_function_privilege('service_role', 'pgmq_public.pop(<same signature as in
migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.pop'), SELECT
ok(has_function_privilege('service_role', 'pgmq_public.send(<same signature as
migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.send'), and
SELECT ok(has_function_privilege('service_role', 'pgmq_public.delete(<same
signature as migration>)', 'EXECUTE'), 'service_role can EXECUTE
pgmq_public.delete') ensuring you use the exact function signatures used in the
migration for pop, send, and delete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd6eeaea-69a1-45b1-a24e-aafd02034e88

📥 Commits

Reviewing files that changed from the base of the PR and between f022593 and 0d71101.

📒 Files selected for processing (2)
  • supabase/migrations/20260517144641_pgmq_public_wrapper.sql
  • supabase/tests/pgmq_public_grants_test.sql

Comment on lines +17 to +20
create schema if not exists pgmq_public;

grant usage on schema pgmq_public to service_role;
grant usage on schema pgmq to service_role;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether this migration explicitly revokes PUBLIC privileges.
rg -n "revoke\\s+all\\s+on\\s+schema\\s+pgmq_public\\s+from\\s+public|revoke\\s+execute\\s+on\\s+all\\s+functions\\s+in\\s+schema\\s+pgmq_public\\s+from\\s+public|grant\\s+execute\\s+on\\s+function|grant\\s+usage\\s+on\\s+schema\\s+pgmq_public" supabase/migrations/20260517144641_pgmq_public_wrapper.sql

Repository: gridaco/grida

Length of output: 140


🏁 Script executed:

cat -n supabase/migrations/20260517144641_pgmq_public_wrapper.sql

Repository: gridaco/grida

Length of output: 4328


🌐 Web query:

PostgreSQL default permissions schema function PUBLIC EXECUTE

💡 Result:

In PostgreSQL, functions are created with the EXECUTE privilege granted to PUBLIC by default [1]. To change this behavior for future functions, you must use the ALTER DEFAULT PRIVILEGES command [2][3]. Crucially, if you want to revoke this default PUBLIC EXECUTE privilege, you must do so globally (without specifying an IN SCHEMA clause) [1][4][5]. Per-schema default privileges are additive to global default privileges; therefore, attempting to revoke privileges in a specific schema will not override the global default that grants EXECUTE to PUBLIC [4][5]. To revoke the default PUBLIC EXECUTE privilege for functions created by a specific role, use the following command: ALTER DEFAULT PRIVILEGES FOR ROLE role_name REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; If you omit the FOR ROLE clause, the command applies to the current role [3][6]. Once this global revocation is in place, you can selectively grant EXECUTE privileges to specific roles or schemas as needed [1]. Note that ALTER DEFAULT PRIVILEGES only affects objects created after the command is executed and does not change privileges on existing functions [2][3].

Citations:


Add explicit REVOKE statements to prevent PUBLIC access to functions.

Functions in PostgreSQL are created with EXECUTE granted to PUBLIC by default. This migration creates wrapper functions without revoking the default PUBLIC EXECUTE privilege, which contradicts the stated intent ("deny-by-default for anon/authenticated"). Add REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC; after the function grants to enforce deny-by-default at the DDL level.

Suggested fix
 grant execute on function
     pgmq_public.pop(text),
     pgmq_public.send(text, jsonb, integer),
     pgmq_public.read(text, integer, integer),
     pgmq_public.archive(text, bigint),
     pgmq_public.delete(text, bigint)
 to service_role;
+revoke execute on all functions in schema pgmq_public from public;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260517144641_pgmq_public_wrapper.sql` around lines 17 -
20, Add an explicit revoke for public execute privileges on the wrapper
functions so PUBLIC cannot call them by default: after you finish
creating/granting the wrapper functions in schema pgmq_public (and similarly for
pgmq if wrappers are created there), add a statement to revoke EXECUTE from
PUBLIC, e.g. use REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM
PUBLIC to enforce deny-by-default for anon/authenticated; ensure this revoke
comes after the grants that assign service_role privileges so service_role
retains access.

@softmarshmallow softmarshmallow merged commit 5b6e8ce into main May 17, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant