n8n-io/n8n · error

Supabase delete failed

Error message

Supabase delete failed: ${error.message}

What it means

Thrown by SupabaseVectorStore.delete when the PostgREST delete call returns a non-null error. Root causes, named in the appended message, are usually RLS denying DELETE, a missing table, an auth/connection failure, or (rarely) a constraint that blocks the delete. Empty id lists short-circuit before this call, so this only fires for a real delete attempt.

Solutions

  1. Inspect the appended message to classify (RLS vs missing relation vs constraint).
  2. For RLS, use the service role key for trusted deletes or add a DELETE policy.
  3. Confirm the table name is correct and the table exists.
  4. For FK/constraint blocks, delete dependent rows first or adjust the constraint.
  5. Retry transient/network errors.

Example fix

// before — anon key, RLS blocks delete
new SupabaseVectorStore('docs', {
  url, apiKey: process.env.SUPABASE_ANON_KEY, tableName: 'docs',
});

// after — service role key, or add a DELETE policy
new SupabaseVectorStore('docs', {
  url, apiKey: process.env.SUPABASE_SERVICE_ROLE_KEY, tableName: 'docs',
});
Defensive patterns

Strategy: retry

Validate before calling

function assertSupabaseDeletable(opts: { url: string; apiKey: string; tableName: string }): void {
  if (!opts.apiKey) throw new Error('Supabase apiKey missing (service role needed for deletes)');
  if (!opts.tableName) throw new Error('Supabase tableName missing');
  if (!/^https?:\/\/.+/.test(opts.url)) throw new Error('Supabase url missing/invalid');
}

assertSupabaseDeletable(opts);

Try / catch

async function deleteWithRetry(store: SupabaseVectorStore, ids: string[], attempts = 3) {
  if (ids.length === 0) return;
  for (let i = 0; i < attempts; i++) {
    try {
      await store.deleteDocuments(ids);
      return;
    } catch (err) {
      const msg = err instanceof Error ? err.message : '';
      const transient = /network|timeout|fetch|ECONN|503|504|paused/i.test(msg);
      if (!transient || i === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, 2 ** i * 200));
    }
  }
}

Prevention

When it happens

Trigger: RLS policy without a DELETE grant for the api key's role; tableName pointing at a non-existent table; service key wrong/expired; Supabase project paused; foreign-key ON DELETE RESTRICT blocking removal (surfaced through PostgREST).

Common situations: Anon key used for deletes (RLS blocks); RLS enabled but no DELETE policy; schema migration renaming the table; FK constraint preventing deletion of referenced rows.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/5b93f8dd6471d424. Report an issue: GitHub.

Appendix: source

Thrown at packages/@n8n/agents/src/vector-stores/supabase.ts:149

			query_embedding: vector,
		});
		const filtered =
			opts.filter && opts.filter.conditions.length > 0
				? applySupabaseFilter(rpcCall, opts.filter)
				: rpcCall;

		const { data, error } = await filtered.limit(opts.topK);
		if (error) throw new Error(`Supabase query failed: ${error.message}`);

		return (data ?? []).map(toQueryResult);
	}

	async delete({ ids }: { ids: string[] }): Promise<void> {
		if (ids.length === 0) return;

		const client = await this.getClient();
		const { error } = await client.from(this.tableName).delete().in('id', ids);
		if (error) throw new Error(`Supabase delete failed: ${error.message}`);
	}

	close(): void {
		this.client = undefined;
	}

	private async getClient(): Promise<SupabaseClient> {
		if (!this.client) {
			const { createClient } = await import('@supabase/supabase-js');
			this.client = createClient(this.constructorOptions.url, this.constructorOptions.apiKey);
		}
		return this.client;
	}
}

function toQueryResult(row: SupabaseMatchRow): VectorQueryResult {
	return {
		id: String(row.id),

View on GitHub (pinned to 5ac6606e81)