supabase-validation
npx skills add https://github.com/visaoenhance/supabase-debug-playground --skill supabase-validation
Agent 安装分布
Skill 文档
This skill is safe for use in any project. It never runs reset/break loops or destructive commands. Those exist only in the supabase-debug-playground teaching harness and are not part of this skill.
Normative Principle
No Evidence. Not done.
Every action this skill governs â write, deploy, migrate, fix â is incomplete until observable, raw output confirms it. This is not a style preference; it is the contract this skill enforces.
Two-tier action classification:
- Destructive actions require environment classification (Rule A) + explicit confirmation before execution.
- High-sensitivity file edits (any file under
supabase/) require showing a diff + confirmation before applying â even if the operation is not destructive by the definition below.
Destructive Operations â Definition
Destructive operations are defined here so the term is not interpreted loosely. Any rule that gates on “destructive” applies to all items in this list:
- Dropping tables or columns
- Resetting the database (
supabase db reset) - Replaying migrations against any live project (local, staging, or production â even local, unless explicitly confirmed disposable)
- Deleting rows or truncating tables
- Overwriting a deployed edge function
- Modifying or dropping RLS policies
- Any write using
service_rolekey
If an operation is not on this list, it is still subject to Rules B and A (evidence and environment classification) but does not require the full destructive-operation confirmation protocol.
Global Clause â Non-Interactive Mode
Treat as non-interactive if any of the following are true:
- No active chat session is available to receive a reply
- Running inside a CI job, automated workflow, or scheduled script
- Agent mode with
autoApprove,--yes, or equivalent flag set - The agent cannot determine whether a reply is possible
If uncertain, treat as non-interactive.
Rule: If user confirmation is required (by any rule below) but the agent is running in a non-interactive context, the agent must:
- Output the exact command, query, or SQL that would be executed
- Explain what it does and why confirmation is required
- Not execute it
A rule requiring confirmation does not become optional because the agent cannot ask. It becomes a manual handoff.
Global Clause â Never Disclose Secrets
Never print, paste, log, or include in a diff:
SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY(or anyservice_rolekey)- JWTs or bearer tokens of any kind
- Any value that begins with
eyJ(base64-encoded JWT) - Any secret from
.env,.env.local, or equivalent files
If the agent needs to confirm a key is present: state “I can see a value is
set for SUPABASE_ANON_KEY.” If identity confirmation is needed, ask the user
to confirm the last 4 characters â do not print them.
Showing SUPABASE_URL is permitted â host portion only (e.g.,
https://xyz.supabase.co). Do not include query strings or path segments that
may carry tokens.
This rule applies even in local development.
Rule A â Environment Classification Gate
Before any write, migration, RLS change, or function deploy, the agent must classify the environment and surface evidence to the user. It must not proceed until the environment type is confirmed.
Classification levels:
localâSUPABASE_URLishttp://localhost:*, orsupabase statusshows local instancestagingâ known non-production remote ref, explicitly confirmed by userproductionâ any*.supabase.coURL or unrecognised project ref
Evidence the agent must display before acting:
- The value of
SUPABASE_URL(host portion only â no keys) - Output of
supabase statusorsupabase projects listshowing the project ref - For production: the ref + any branch/context information available
Enforcement:
localâ agent may proceed with non-destructive validation commands autonomously. Non-destructive examples:SELECTschema inspection queries,supabase functions logs,curl/ HTTP request replays,git diff,supabase gen types typescript --local.stagingâ agent must state the environment and confirm intent before writingproductionâ agent must surface the full command for user review and require explicit “yes, proceed” before executing anything that writes or modifies schema- If environment cannot be determined â treat as production; do not execute
No Environment Assumptions (see also Rule G): The agent must never infer
environment type from file presence alone â the existence of .env,
supabase/config.toml, or a supabase/ directory does not confirm the
environment is local. Environment classification must be based only on runtime
evidence: the SUPABASE_URL value, output of supabase status, or an explicit
user statement. Rule G extends this constraint to all tasks, including read-only
inspection â not only pre-action gates.
Rule B â No Silent Writes
An agent must never perform a write operation and report completion without returning observable evidence that the write succeeded.
Required evidence by operation type:
| Operation | Required evidence |
|---|---|
INSERT |
Returned row with id from .insert().select() |
UPDATE / DELETE |
Row count or returned rows confirming the change |
| RLS policy change | Output of SELECT policyname, cmd FROM pg_policies WHERE tablename = '...' |
| Edge function deploy | HTTP response payload containing request_id and ok: true |
| Migration | supabase db diff or information_schema.columns query confirming column/table exists |
| Schema type regen | git diff supabase/types.gen.ts output (even if empty â show it) |
If the operation produces no returnable evidence (e.g., Prefer: return=minimal),
re-run the operation with .select() chained, or run a follow-up read query.
Surface the raw output. Do not summarise before showing it. Return the actual payload â row object, JSON body, query result, diff â so the user can verify directly. You may summarise after showing raw output.
// Bad
"Insert successful."
// Good
{ id: 42, created_at: "2026-02-28T20:14:22Z" }
// (then) "Insert confirmed â row id 42 returned."
Rule C â service_role Restricted Use
service_role bypasses all RLS policies. Its use must be explicitly bounded.
Permitted without user confirmation:
- Read-only diagnostics only â
SELECTqueries to inspect schema, policies, or data for debugging purposes
Require explicit user confirmation before use:
- Any write, insert, update, or delete using
service_role - Any operation that modifies schema or policies using
service_role
Confirmation protocol:
- State clearly: “This operation requires
service_rolewhich bypasses RLS.” - Display the environment classification (Rule A) as part of the request
- Show the exact command or query that will be run
- Wait for explicit “yes” â do not infer consent from prior approval of a related step
- If non-interactive: output the command for manual review, do not execute
Rule D â Safe Fallback When Tooling Is Unavailable
The agent must not hallucinate access it doesn’t have.
If a required tool is unavailable:
- State the missing capability explicitly
- Propose a non-destructive alternative:
- Instead of CLI logs â capture
error.code,error.message,error.hintfrom the client SDK response - Instead of
supabase statusâ ask user to run it and paste the output - Instead of DB introspection â provide the exact SQL for the user to run in the dashboard SQL editor
- Instead of CLI logs â capture
- Never invent log output, query results, or command output
- Never report a validation as passed unless the agent itself received and parsed the response
Prohibited:
- “I checked the logs and there are no errors” without having received log output
- “The migration ran successfully” without confirmed evidence
- “The policy is in place” without
pg_policiesoutput showing it
Rule E â Minimal File Mutations
Required behaviour:
- Prefer the smallest diff that achieves the fix
- Before applying any file change, show a diff or describe line-level changes
- Never use a “replace entire file” pattern unless the user explicitly requests it
- For Supabase-specific files (
supabase/functions/**,supabase/migrations/**,supabase/types.gen.ts): treat all changes as high-sensitivity and show the diff before applying
Why Supabase files are high-sensitivity:
- Overwriting a migration file that has already run creates drift â it does not undo the migration
- Overwriting
types.gen.tswith stale content breaks TypeScript without a compile error - Overwriting an edge function’s
index.tswith an earlier version silently reverts a fix
Permitted without confirmation: adding new files where no existing file is
displaced.
Requires confirmation: modifying or deleting any existing file under
supabase/.
Rule G â No Hidden Context Assumption
The agent must not infer Supabase project configuration from local file presence
alone. The existence of .env, supabase/config.toml, or a supabase/
directory does not confirm which project is active, whether it is local or
remote, or whether the configuration applies to the current task.
Environment classification must be based only on runtime evidence:
- The resolved value of
SUPABASE_URL(from the active shell environment â not a.envfile the agent reads in isolation) - Output of
supabase statusorsupabase projects list - An explicit user statement (e.g., “I’m working against our staging project”)
What this prevents:
- A skill applied inside a monorepo where a
supabase/folder exists but belongs to a different sub-project - An agent assuming
localbecause it seeshttp://localhostin a.envfile that may be stale or inactive - Hallucinated project context: “this looks like a local project” based on file presence alone
Required agent phrasing: “I can see a supabase/ directory and a .env
file. To classify the environment I need to confirm the active SUPABASE_URL.
Running supabase status⦔ â not “This looks like a local project.”
Relationship to Rule A: Rule G governs what the agent may assume at any point, including read-only tasks. Rule A governs what the agent must confirm before acting. Rule G is the precondition; Rule A is the gate.
Activation Rule â When This Skill Applies
This skill MUST activate automatically whenever any of the following signals appear in code, config, or commands:
supabase-jsis imported or usedsupabase.rpc()is called.from(...).insert(),.update(), or.delete()is usedCREATE FUNCTION,CREATE POLICY, orALTER TABLEappears in SQLsupabase functions deployis runsupabase gen typesis relevant to the task- Any migration touches schema or RLS
- Any file under
supabase/functions/is modified - Any file under
supabase/migrations/orsupabase/seed.sqlis modified - Any
supabase db *command is run (push/reset/diff)
When activated:
- Add a validation step to the task plan before execution begins
- Execute that validation step before reporting completion
- Do not ask the user whether to validate â validate automatically
- If execution is not possible, state that explicitly and provide the exact commands for the user to run
Validation Contract
A Supabase task is not complete when code compiles or a command exits 0. It is complete only when the validation step passes.
Definition of done:
- Failure or baseline confirmed (run the current behaviour first)
- Fix applied
- Validation executed
- Validation produces a binary pass result
Required plan shape:
- Run current behaviour â confirm failure if fixing, baseline if building
- Diagnose â read the actual error (code, message, hint)
- Apply minimal fix
- Verify â binary pass/fail, not eyeballing
- Report completion
Hard gates â none of these are optional:
- “Looks correct” is not a valid success signal
- “Exit code 0” alone is not a valid success signal
- Never skip verification because the code looks right â run the check
- If a verify step fails, diagnose before retrying â do not loop without reading the error
Pattern Index
| Pattern | Trigger | Passes when |
|---|---|---|
| 1 â Edge function (local) | Change to a function running via supabase functions serve |
HTTP 200 + body.ok === true + request_id present |
| 2 â Edge function (production) | supabase functions deploy <name> to a real project |
HTTP 200 + body.ok === true + request_id present |
| 3 â RPC | CREATE OR REPLACE FUNCTION or migration modifying an RPC |
No error + response contains expected fields |
| 4 â CRUD / Insert | Any .insert(), .update(), .delete() via supabase-js |
Non-null array with id present |
| 5 â RLS | CREATE POLICY, DROP POLICY, ENABLE ROW LEVEL SECURITY |
All 3 roles pass: unauthed blocked + authed allowed + service_role allowed |
| 6 â Schema migration | Migration adding, removing, or renaming a column | types.gen.ts reflects live schema + file committed |
Pattern 1 â Edge Function (Local)
Trigger: after any change to a Supabase edge function running locally via
supabase functions serve.
Validation steps:
- POST to
http://localhost:54321/functions/v1/<name> - Assert HTTP 200
- Assert response body is valid JSON with
ok: true - Assert
request_idis present in the response body
Fail signal: HTTP 500, empty body, no request_id, or
Deno.env.get(...) returning undefined.
Diagnostic: errors stream to the supabase functions serve terminal â
read them directly. There is no supabase functions logs command for local dev.
Docs:
Do not report done until: HTTP 200 + ok: true + request_id confirmed.
Pattern 2 â Edge Function (Production)
Trigger: after supabase functions deploy <name> to a real Supabase project.
Validation steps:
- POST to
$SUPABASE_URL/functions/v1/<name>withAuthorization: Bearer $SUPABASE_ANON_KEY(do not print the key value) - Assert HTTP 200
- Assert response body is valid JSON with
ok: true - Assert
request_idis present in the response body
Fail signal: HTTP 500 or unstructured body.
Diagnostic: dashboard function logs:
https://supabase.com/dashboard/project/<PROJECT_REF>/functions/<name>/logs
Docs:
Do not report done until: HTTP 200 + ok: true + request_id confirmed
against the production URL.
Pattern 3 â RPC
Trigger: after any CREATE OR REPLACE FUNCTION or migration that modifies
an RPC.
Validation steps:
- Call via supabase-js:
supabase.rpc('<function_name>', { ...args }) - Assert
erroris null - Assert response data contains the expected fields
Fail signal: error.code is present â read error.code, error.message,
error.hint as a unit.
Diagnostic: if error code is 42703 (undefined column), run:
SELECT pg_get_functiondef('<schema>.<function_name>(<arg_types>)'::regprocedure);
Docs:
Do not report done until: RPC returns no error + response shape is correct.
Pattern 4 â CRUD / Insert
Trigger: after any insert, update, or delete via supabase-js.
Validation steps:
- Always chain
.select().throwOnError()onto the operation - Assert returned
datais a non-null array - Assert the array contains a row with
id
Why this matters: .insert() without .select() sends
Prefer: return=minimal â PostgREST returns 204 with empty body. supabase-js
translates this to { data: null, error: null }. This is not confirmation the
row was saved.
Correct pattern:
const { data } = await supabase
.from("table")
.insert({ ...values })
.select()
.throwOnError();
// data is a non-null array if the insert succeeded
Docs:
Do not report done until: insert returns a non-null array containing a row
with id.
Pattern 5 â RLS
Trigger: after any CREATE POLICY, DROP POLICY,
ALTER TABLE ... ENABLE ROW LEVEL SECURITY, or migration touching RLS.
Validation steps â all three must pass:
- Unauthenticated anon â attempt the restricted operation with a plain anon
key and no auth session â assert blocked (error
42501) - Authenticated user â same operation with anon key + valid JWT â assert allowed
- service_role â same operation with service_role key â assert allowed (service_role always bypasses RLS)
Diagnostic commands:
-- See all policies on a table
SELECT policyname, cmd, qual, with_check
FROM pg_policies WHERE tablename = '<table>';
-- Confirm RLS is enabled
SELECT relname, relrowsecurity FROM pg_class WHERE relname = '<table>';
Key insight: if something works from the Supabase dashboard but fails in the
app, check whether an INSERT policy exists for the role your app uses.
service_role has BYPASSRLS â it skips policy evaluation entirely.
Docs:
Do not report done until: all three scenarios produce the expected result.
Pattern 6 â Schema Migration / Type Drift
Trigger: after any migration that adds, removes, or renames a column.
Validation steps:
- Run:
supabase gen types typescript --local > supabase/types.gen.ts - Run:
git diff supabase/types.gen.ts - If diff is non-empty â expected (new column). Commit the updated file.
- If diff is empty and you added a column â migration may not have run â check.
Drift detection:
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = '<table>'
ORDER BY ordinal_position;
Why this matters: TypeScript compiles cleanly against stale types. A column
that exists in the DB but not in types.gen.ts is simply invisible to your code
â no compile error, silent bugs.
CI: run supabase gen types typescript --local > supabase/types.gen.ts && git diff --exit-code supabase/types.gen.ts after any migration deploy. Fail the
build if the diff is non-empty.
Docs:
Do not report done until: types.gen.ts reflects the current live schema
and the file is committed.