Can Agents Run It? · evidence — Issue 1: Supabase

claude-opus-5 · d2_webhooks · rep 1

pass
model
claude-opus-5
task
d2_webhooks
rep
1
day
2
steps
21
tokens
83,416
est. cost
$1.001
agent time
117s
wave
s4b
started
2026-07-25T06:36:13Z

The task as the agent received it

You have operator access to a hosted Supabase project. Credentials are in environment variables: SUPABASE_URL, SUPABASE_PROJECT_REF, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_DB_URL, SUPABASE_DB_PASSWORD. Every INSERT into the existing table public.signups must deliver an HTTP webhook: a POST request with a JSON body containing at least the new row's email and plan values, sent to the receiver endpoint {SUPABASE_URL}/functions/v1/hook-recorder (an edge function that is already deployed on this project; SUPABASE_URL is in your environment). The receiver requires a valid project JWT in the Authorization header and records every payload it accepts. Implement the delivery from the database itself, so it fires for ANY insert into signups no matter which client performs it — no external workers, no cron, no client-side code. Delivery may be asynchronous (a few seconds is fine). Prove it end-to-end with a test insert before finishing: the receiver stores accepted payloads in public.webhook_receipts.

What it ran — 21 commands

#1exit 0
env | grep -i supabase | sed 's/\(KEY=.\{20\}\).*/\1.../' 
SUPABASE_DB_PASSWORD=<redacted:SUPABASE_DB_PASSWORD>
SUPABASE_DB_URL=<redacted:SUPABASE_DB_URL>
SUPABASE_URL=https://<ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIs...
SUPABASE_PROJECT_REF=<ref>
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
#2exit 0
which psql curl jq python3; psql --version 2>/dev/null
/usr/bin/psql
/usr/bin/curl
/usr/bin/jq
/usr/bin/python3
psql (PostgreSQL) 18.4 (Debian 18.4-1.pgdg12+1)
#3exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "\d public.signups" -c "\d public.webhook_receipts" 2>&1 | head -60
                                     Table "public.signups"
   Column   |           Type           | Collation | Nullable |             Default              
------------+--------------------------+-----------+----------+----------------------------------
 id         | bigint                   |           | not null | generated by default as identity
 email      | text                     |           | not null | 
 plan       | text                     |           | not null | 'free'::text
 created_at | timestamp with time zone |           | not null | now()
Indexes:
    "signups_pkey" PRIMARY KEY, btree (id)

                                 Table "public.webhook_receipts"
   Column    |           Type           | Collation | Nullable |             Default              
-------------+--------------------------+-----------+----------+----------------------------------
 id          | bigint                   |           | not null | generated by default as identity
 received_at | timestamp with time zone |           | not null | now()
 payload     | jsonb                    |           | not null | 
Indexes:
    "webhook_receipts_pkey" PRIMARY KEY, btree (id)

#4exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select extname, extversion, n.nspname from pg_extension e join pg_namespace n on n.oid=e.extnamespace;" 2>&1
      extname       | extversion |  nspname   
--------------------+------------+------------
 plpgsql            | 1.0        | pg_catalog
 supabase_vault     | 0.3.1      | vault
 pgcrypto           | 1.3        | extensions
 uuid-ossp          | 1.1        | extensions
 pg_stat_statements | 1.11       | extensions
(5 rows)

#5exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select name, default_version, installed_version from pg_available_extensions where name in ('pg_net','http','pg_cron','supabase_vault','wrappers') order by name;" 2>&1
      name      | default_version | installed_version 
----------------+-----------------+-------------------
 http           | 1.6             | 
 pg_cron        | 1.6.4           | 
 pg_net         | 0.20.4          | 
 supabase_vault | 0.3.1           | 0.3.1
 wrappers       | 0.6.2           | 
(5 rows)

#6exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select current_user, session_user, version();" 2>&1
 current_user | session_user |                                   version                                    
--------------+--------------+------------------------------------------------------------------------------
 postgres     | postgres     | PostgreSQL 17.6 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 15.2.0, 64-bit
(1 row)

#7exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "create extension if not exists pg_net with schema extensions;" -c "select extname, extversion, n.nspname from pg_extension e join pg_namespace n on n.oid=e.extnamespace where extname='pg_net';" 2>&1
CREATE EXTENSION
 extname | extversion |  nspname   
---------+------------+------------
 pg_net  | 0.20.4     | extensions
(1 row)

#8exit 0
cd /tmp && curl -s -o /tmp/probe.txt -w "HTTP %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY" -H "Content-Type: application/json" -d '{"email":"probe@example.com","plan":"probe"}'; cat /tmp/probe.txt; echo; echo "--- no auth ---"; curl -s -o /tmp/probe2.txt -w "HTTP %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Content-Type: application/json" -d '{"email":"noauth@example.com","plan":"x"}'; cat /tmp/probe2.txt
HTTP 200
{"ok":true}
--- no auth ---
HTTP 401
{"code":"UNAUTHORIZED_NO_AUTH_HEADER","message":"Missing authorization header"}
#9exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select id, received_at, payload from public.webhook_receipts order by id desc limit 5;" 2>&1
 id |          received_at          |                     payload                     
----+-------------------------------+-------------------------------------------------
  2 | 2026-07-25 06:36:56.530727+00 | {"plan": "probe", "email": "probe@example.com"}
(1 row)

#10exit 0
cd /tmp && echo "--- anon key ---"; curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Authorization: Bearer $SUPABASE_ANON_KEY" -H "Content-Type: application/json" -d '{"email":"anonprobe@example.com","plan":"probe"}'; echo "--- bogus jwt ---"; curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/functions/v1/hook-recorder" -H "Authorization: Bearer not.a.jwt" -H "Content-Type: application/json" -d '{"email":"bogus@example.com","plan":"x"}'
--- anon key ---
{"ok":true}
HTTP 200
--- bogus jwt ---
{"code":"UNAUTHORIZED_INVALID_JWT_FORMAT","message":"Invalid JWT"}
HTTP 401
#11exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "\df vault.create_secret" -c "\d vault.decrypted_secrets" 2>&1 | head -40
                                                                                List of functions
 Schema |     Name      | Result data type |                                                     Argument data types                                                      | Type 
--------+---------------+------------------+------------------------------------------------------------------------------------------------------------------------------+------
 vault  | create_secret | uuid             | new_secret text, new_name text DEFAULT NULL::text, new_description text DEFAULT ''::text, new_key_id uuid DEFAULT NULL::uuid | func
(1 row)

                        View "vault.decrypted_secrets"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 id               | uuid                     |           |          | 
 name             | text                     |           |          | 
 description      | text                     |           |          | 
 secret           | text                     |           |          | 
 decrypted_secret | text                     | C         |          | 
 key_id           | uuid                     |           |          | 
 nonce            | bytea                    |           |          | 
 created_at       | timestamp with time zone |           |          | 
 updated_at       | timestamp with time zone |           |          | 

#12exit 0
cd /tmp && cat > /tmp/secrets.sql <<'EOF'
\set ON_ERROR_STOP on
select vault.create_secret(:'hook_url',  'signups_webhook_url', 'Endpoint for signups INSERT webhook')
  where not exists (select 1 from vault.secrets where name = 'signups_webhook_url');
select vault.create_secret(:'hook_key',  'signups_webhook_key', 'Project JWT used to authenticate to hook-recorder')
  where not exists (select 1 from vault.secrets where name = 'signups_webhook_key');
select name, description, created_at from vault.secrets order by name;
EOF
psql "$SUPABASE_DB_URL" -v hook_url="$SUPABASE_URL/functions/v1/hook-recorder" -v hook_key="$SUPABASE_SERVICE_ROLE_KEY" -f /tmp/secrets.sql 2>&1
 create_secret 
---------------
(0 rows)

            create_secret             
--------------------------------------
 81b65935-c1f0-4914-80c3-c0d1fcf83be8
(1 row)

        name         |                        description                         |          created_at           
---------------------+------------------------------------------------------------+-------------------------------
 hook_recorder_token | Bearer token (project JWT) for hook-recorder edge function | 2026-07-24 05:09:11.531316+00
 hook_recorder_url   | URL for the signups insert webhook receiver                | 2026-07-24 05:09:10.696842+00
 project_url         | Base URL of this Supabase project                          | 2026-07-24 05:07:11.304907+00
 service_role_key    | Service role JWT used for internal webhook calls           | 2026-07-24 05:07:11.421055+00
 signups_webhook_jwt | JWT for public.signups insert webhook                      | 2026-07-24 05:58:09.700696+00
 signups_webhook_key | Project JWT used to authenticate to hook-recorder          | 2026-07-25 06:37:22.098916+00
 signups_webhook_url | Endpoint for public.signups insert webhook                 | 2026-07-24 05:58:09.499445+00
(7 rows)

#13exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select t.tgname, c.relname, p.proname, t.tgenabled, pg_get_triggerdef(t.oid) as def from pg_trigger t join pg_class c on c.oid=t.tgrelid join pg_proc p on p.oid=t.tgfoid where not t.tgisinternal and c.relname='signups';" 2>&1
 tgname | relname | proname | tgenabled | def 
--------+---------+---------+-----------+-----
(0 rows)

#14exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select n.nspname, p.proname from pg_proc p join pg_namespace n on n.oid=p.pronamespace where n.nspname in ('public','net','extensions','supabase_functions') and p.proname ilike '%hook%' or (n.nspname='public' and p.proname ilike '%signup%');" -c "select name, decrypted_secret from vault.decrypted_secrets where name in ('signups_webhook_url','signups_webhook_jwt','hook_recorder_url');" 2>&1
 nspname | proname 
---------+---------
(0 rows)

        name         |                                                                                                 decrypted_secret                                                                                                 
---------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 hook_recorder_url   | https://<ref>.supabase.co/functions/v1/hook-recorder
 signups_webhook_url | https://<ref>.supabase.co/functions/v1/hook-recorder
 signups_webhook_jwt | <redacted:SUPABASE_ANON_KEY>
(3 rows)

#15exit 0
cd /tmp && cat > /tmp/webhook.sql <<'EOF'
\set ON_ERROR_STOP on
begin;

-- Trigger function: fires the webhook asynchronously via pg_net.
-- SECURITY DEFINER + locked-down search_path so any caller (anon, authenticated,
-- service_role, SQL, dashboard) triggers delivery without needing vault/net rights.
create or replace function public.signups_webhook_notify()
returns trigger
language plpgsql
security definer
set search_path = ''
as $fn$
declare
  v_url   text;
  v_token text;
  v_req   bigint;
begin
  select decrypted_secret into v_url
    from vault.decrypted_secrets where name = 'signups_webhook_url';
  select decrypted_secret into v_token
    from vault.decrypted_secrets where name = 'signups_webhook_key';

  if v_url is null or v_token is null then
    raise warning 'signups_webhook_notify: missing vault secret, skipping delivery';
    return null;
  end if;

  -- Asynchronous: queued for the pg_net background worker, so the INSERT
  -- transaction never blocks on or fails because of the HTTP call.
  select net.http_post(
           url     => v_url,
           body    => jsonb_build_object(
                        'type',   'INSERT',
                        'table',  'signups',
                        'id',     new.id,
                        'email',  new.email,
                        'plan',   new.plan,
                        'created_at', new.created_at
                      ),
           headers => jsonb_build_object(
                        'Content-Type',  'application/json',
                        'Authorization', 'Bearer ' || v_token
                      ),
           timeout_milliseconds => 5000
         ) into v_req;

  return null;
end;
$fn$;

revoke all on function public.signups_webhook_notify() from public;

drop trigger if exists signups_webhook_after_insert on public.signups;

-- AFTER INSERT ... FOR EACH ROW: fires for every inserted row, any client.
create trigger signups_webhook_after_insert
  after insert on public.signups
  for each row
  execute function public.signups_webhook_notify();

commit;
EOF
psql "$SUPABASE_DB_URL" -f /tmp/webhook.sql 2>&1
BEGIN
CREATE FUNCTION
REVOKE
psql:/tmp/webhook.sql:53: NOTICE:  trigger "signups_webhook_after_insert" for relation "public.signups" does not exist, skipping
DROP TRIGGER
CREATE TRIGGER
COMMIT
#16exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select tgname, tgenabled, pg_get_triggerdef(oid) from pg_trigger where tgrelid='public.signups'::regclass and not tgisinternal;" 2>&1
            tgname            | tgenabled |                                                         pg_get_triggerdef                                                         
------------------------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------
 signups_webhook_after_insert | O         | CREATE TRIGGER signups_webhook_after_insert AFTER INSERT ON public.signups FOR EACH ROW EXECUTE FUNCTION signups_webhook_notify()
(1 row)

#17exit 0
cd /tmp && psql "$SUPABASE_DB_URL" -c "select pid, backend_type, application_name, state from pg_stat_activity where backend_type ilike '%pg_net%' or application_name ilike '%pg_net%';" -c "show shared_preload_libraries;" 2>&1
 pid  |     backend_type     | application_name | state 
------+----------------------+------------------+-------
 4251 | pg_net 0.20.4 worker | pg_net 0.20.4    | idle
(1 row)

                                                     shared_preload_libraries                                                      
-----------------------------------------------------------------------------------------------------------------------------------
 pg_stat_statements, pgaudit, plpgsql, plpgsql_check, pg_cron, pg_net, pgsodium, auto_explain, pg_tle, plan_filter, supabase_vault
(1 row)

#18exit 0
cd /tmp && TS=$(date +%s) && echo "marker: sqltest-$TS@example.com" && psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 -c "insert into public.signups (email, plan) values ('sqltest-$TS@example.com','pro') returning id, email, plan;" 2>&1 && echo "$TS" > /tmp/ts_sql
marker: sqltest-1784961478@example.com
 id |             email              | plan 
----+--------------------------------+------
  1 | sqltest-1784961478@example.com | pro
(1 row)

INSERT 0 1
#19exit 0
cd /tmp && sleep 6 && TS=$(cat /tmp/ts_sql) && psql "$SUPABASE_DB_URL" -c "select id, status_code, content::text from net._http_response order by id desc limit 3;" -c "select id, received_at, payload from public.webhook_receipts where payload->>'email' = 'sqltest-$TS@example.com';" 2>&1
 id | status_code |   content   
----+-------------+-------------
  1 |         200 | {"ok":true}
(1 row)

 id |          received_at          |                                                                           payload                                                                           
----+-------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
  4 | 2026-07-25 06:37:59.477205+00 | {"id": 1, "plan": "pro", "type": "INSERT", "email": "sqltest-1784961478@example.com", "table": "signups", "created_at": "2026-07-25T06:37:59.078709+00:00"}
(1 row)

#20exit 0
cd /tmp && TS=$(date +%s) && echo "$TS" > /tmp/ts_rest && echo "=== anon REST insert ===" && curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/signups" -H "apikey: $SUPABASE_ANON_KEY" -H "Authorization: Bearer $SUPABASE_ANON_KEY" -H "Content-Type: application/json" -H "Prefer: return=representation" -d "{\"email\":\"anon-$TS@example.com\",\"plan\":\"team\"}"
=== anon REST insert ===
[{"id":2,"email":"anon-1784961498@example.com","plan":"team","created_at":"2026-07-25T06:38:18.383497+00:00"}]
HTTP 201
#21exit 0
cd /tmp && TS=$(date +%s) && echo "$TS" > /tmp/ts_batch && echo "=== service_role batch insert (2 rows) ===" && curl -s -w "\nHTTP %{http_code}\n" -X POST "$SUPABASE_URL/rest/v1/signups" -H "apikey: $SUPABASE_SERVICE_ROLE_KEY" -H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY" -H "Content-Type: application/json" -H "Prefer: return=representation" -d "[{\"email\":\"svc-a-$TS@example.com\",\"plan\":\"enterprise\"},{\"email\":\"svc-b-$TS@example.com\"}]"
=== service_role batch insert (2 rows) ===
{"code":"PGRST102","details":null,"hint":null,"message":"All object keys must match"}
HTTP 400

What it said at the end

Anon REST insert succeeded (RLS permits it). Let me also test service_role and a multi-row batch insert to confirm per-row firing.

13 earlier narration turn(s) are in the raw transcript.

How it was graded

A shell script executed against the live project and asserted on the state the agent left behind. No model judged another model.

exit 0 OK

Check it yourself

Raw transcript · Task definition and verifier · Every attempt