5/13 ローンチ予定!
PiloTube

PiloTube 開発日誌

← 「ひとり社長のAI開発記」一覧へ

たった1行の設定忘れで本番ログイン全滅した話

約7分で読めます

Supabase trigger関数の SET search_path TO public 忘れで本番ログイン全滅した話

本番環境で新規ユーザー登録が完全に死んだ。エラーメッセージはただ一言、Database error saving new user。原因を突き止めるのに数時間かかった。そしてまったく同じ罠を、2回踏んだ。


新規登録が全滅した朝

PiloTube(パイロチューブ)の開発が佳境に入ってきたある日、本番環境でユーザー登録のテストをしようとした。メールアドレスを入力して送信ボタンを押す。

エラー。

Database error saving new user

最初は「あ、また何かミスったかな」くらいの感覚だった。ローカルでは動いている。Supabase のダッシュボードを開いて、Auth のログを確認する。エラーは確かに出ている。でも詳細がほとんどない。

「Supabase のサーバー側の問題か?」と思いながら、Postgres のログを掘り始めた。


原因は trigger 関数の search_path 設定漏れ

数時間後、ようやく原因にたどり着いた。

Supabase では、ユーザーが登録されると auth.users テーブルにレコードが挿入される。それをトリガーに、public.users テーブルにプロフィールを自動作成する trigger 関数を自分は書いていた。よくある構成だ。

問題はその trigger 関数が SECURITY DEFINER で定義されていたこと。

SECURITY DEFINER とは、「関数を呼び出したユーザーの権限ではなく、関数を定義したユーザー(多くの場合 postgres)の権限で実行する」という設定だ。RLS(Row Level Security / Supabaseのアクセス制御機能)をバイパスするためによく使われる。

この SECURITY DEFINER 関数には、セキュリティ上の理由から search_path を明示的に設定する必要がある。設定しないと、関数の実行時に search_path が意図しない状態になり、テーブルが見つからなくなったり、想定外のスキーマを参照したりする。

自分が書いていた trigger 関数はこんな感じだった。

CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  INSERT INTO public.users (id, email)
  VALUES (NEW.id, NEW.email);
  RETURN NEW;
END;
$$;

一見問題なさそうに見える。でも足りない

正しくはこう書かなければいけない。

CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO public  -- ← これが必要
AS $$
BEGIN
  INSERT INTO public.users (id, email)
  VALUES (NEW.id, NEW.email);
  RETURN NEW;
END;
$$;

SET search_path TO public の1行が抜けていただけで、本番の新規登録が全滅していた。


ローカルでは動いていた理由

これが厄介なところで、ローカル環境では再現しなかった

Supabase のローカル開発環境(Docker ベース)と本番環境では、Postgres の設定がわずかに異なる。本番環境では search_path のデフォルト設定がより厳格になっていることがある。だからローカルでは動いて、本番でだけ落ちる。

「本番でだけ落ちる」は、デバッグとして最悪の部類だ。再現できないから、手がかりが少ない。


気づきのきっかけ

Supabase の公式ドキュメントをあらためて読み直していたとき、SECURITY DEFINER 関数に関する注意書きを見つけた。

For security reasons, you should set the search_path when creating SECURITY DEFINER functions.

「セキュリティ上の理由から」と書いてある。セキュリティのための設定が、機能の破壊を引き起こしていた。

ドキュメントには確かに書いてあった。自分が読み飛ばしていただけだ。


同じ罠を2回踏んだ

修正して、本番でテスト。動いた。よし、と思って次の機能開発に進んだ。

数週間後。別の trigger 関数を追加した。今度は public.users の更新時に別テーブルを同期する処理だった。

本番でテスト。

エラー。

……また SET search_path TO public が抜けていた。

正直、声が出た。「また同じやつか」と。

1回目は「知らなかった」で済む。2回目は「忘れた」だ。自分の中でルール化できていなかった。


対策として決めたこと

同じミスを3回しないために、trigger 関数を書くときのテンプレートを固定することにした。

-- trigger 関数テンプレート(SECURITY DEFINER 版)
CREATE OR REPLACE FUNCTION public.関数名()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO public  -- 必須。忘れると本番で死ぬ
AS $$
BEGIN
  -- 処理
  RETURN NEW;
END;
$$;

このコメント「必須。忘れると本番で死ぬ」は、将来の自分への警告として残している。

また、新しい trigger 関数を追加したときは、必ずローカルと本番の両方で登録フローを通しでテストすることをルールにした。「ローカルで動いたから大丈夫」は、Supabase の本番環境には通用しない。


PiloTube 開発での教訓

PiloTube はまだ開発中のサービスだ。ユーザー数が少ないうちにこういうバグを踏めたのは、ある意味ラッキーだったとも言える。本番でユーザーが増えてから同じミスをしていたら、もっとまずかった。

ただ、2回踏んだのはまずい。

エラーメッセージが Database error saving new user という素っ気ない一言しか出ないのも、デバッグを難しくしている要因だった。Supabase の Auth 周りのエラーは、詳細が Postgres ログの奥深くに埋まっていることが多い。次からは最初に Postgres ログを見に行く、という順番を身体に染み込ませた。


読者へのヒント

Supabase で trigger 関数を書くなら、以下を頭に入れておいてほしい。

SECURITY DEFINER を使うなら SET search_path TO public は必須 書き忘れると、本番環境でだけ落ちる可能性がある。ローカルで動いていても安心できない。

② エラー Database error saving new user が出たら Postgres ログを掘れ Supabase ダッシュボードの Auth ログだけでは情報が足りないことが多い。Supabase Studio の「Logs」→「Postgres」を確認する。

③ trigger 関数はテンプレートを固定しろ 毎回ゼロから書くと、必ずどこかで設定が抜ける。コメント付きのテンプレートをスニペットとして保存しておくのが一番手っ取り早い。

④ 本番環境でのフロー通しテストをサボらない 「ローカルで動いた」は「本番で動く」を保証しない。特に Supabase の Auth 周りは、本番とローカルで挙動が微妙に違う。

同じ罠を踏んでいる人が、世界のどこかに絶対いると思って書いた。少しでも役に立てば。

チャプター生成AI

URL貼るだけ。AIがチャプターを自動生成。

1

YouTubeのURLをコピーして貼る

2

「生成する」を押す

3

概要欄にコピペして完了

無料でチャプターを生成する →

月3回まで無料 · クレジットカード不要