Merge pull request #346 from ArnasDon/fix/ai-autoreply-slot-grant

fix(ai): grant EXECUTE on claim_ai_reply_slot so auto-reply works (#345)
This commit is contained in:
Arnas Donauskas 2026-07-07 10:46:08 +03:00 committed by GitHub
commit 2ebdc2ebf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 46 additions and 1 deletions

View File

@ -122,7 +122,15 @@ export async function dispatchInboundToAiReply(
max_replies: config.autoReplyMaxPerConversation,
},
)
if (claimErr || claimed !== true) return
if (claimErr) {
// A real error here (vs. losing the cap race) is almost always a
// deploy issue — e.g. `claim_ai_reply_slot` not EXECUTE-able by the
// service role, or the migration not applied. Log it loudly: a
// silent return makes "auto-reply never fires" undiagnosable.
console.error('[ai auto-reply] claim_ai_reply_slot failed:', claimErr)
return
}
if (claimed !== true) return // lost the per-conversation cap race
await engineSendText({
accountId,

View File

@ -129,3 +129,13 @@ RETURNS boolean AS $$
)
SELECT EXISTS (SELECT 1 FROM claimed);
$$ LANGUAGE sql SECURITY DEFINER SET search_path = public;
-- The auto-reply bot claims slots under the service-role client (the
-- inbound webhook has no auth.uid()), so it needs EXECUTE. SECURITY
-- DEFINER alone is not enough — it sets the privileges the function runs
-- *with*, not who may call it. Without this grant the RPC fails with
-- permission-denied on instances where the default PUBLIC execute
-- privilege has been revoked (hardened / self-hosted Supabase), and the
-- bot silently never replies. Only the service role claims slots, so we
-- grant to it alone (mirrors 007 / 012). See migration 031 / issue #345.
GRANT EXECUTE ON FUNCTION public.claim_ai_reply_slot(uuid, integer) TO service_role;

View File

@ -0,0 +1,27 @@
-- ============================================================
-- 031_ai_reply_slot_grant.sql — fix: AI auto-reply never fires
--
-- Migration 029 created `claim_ai_reply_slot(uuid, integer)` as a
-- SECURITY DEFINER function but never GRANTed EXECUTE on it — the only
-- function in the schema missing its grant (cf. 007, 012, 018, 019,
-- 025, 030, which all grant EXECUTE explicitly).
--
-- SECURITY DEFINER changes the privileges a function runs *with*, not
-- who may *call* it: the caller still needs EXECUTE. On Postgres
-- instances where the default PUBLIC execute privilege on public-schema
-- functions has been revoked (standard on hardened / self-hosted
-- Supabase), `service_role` therefore cannot invoke it. The AI
-- auto-reply path runs entirely under the service-role client (the
-- inbound webhook has no auth.uid()), so `db.rpc('claim_ai_reply_slot')`
-- fails with permission-denied, the caller bails before sending, and the
-- bot silently never answers ANY inbound message — while the Playground
-- (which never claims a slot) keeps working. See issue #345.
--
-- Only the service role ever claims a slot, so we grant to it alone —
-- matching the increment-counter precedent in 007 / 012, and never
-- exposing a counter-mutating function to end users.
--
-- Idempotent — GRANT is a no-op when the privilege already exists.
-- ============================================================
GRANT EXECUTE ON FUNCTION public.claim_ai_reply_slot(uuid, integer) TO service_role;