しんたろーのITアカデミー
開発日記

SNSに同じ投稿が2回流れる。Claude CodeでのAI開発で直面したエンジニアの悪夢

SNSに同じ投稿が2回流れる。Claude CodeでのAI開発で直面したエンジニアの悪夢
しんたろーしんたろー
15分で読めます
この記事の内容(目次)

リトライボタンを押した。SNSのタイムラインに、まったく同じ投稿が2つ並んだ。

血の気が引いた。ユーザーの信頼を一瞬で吹き飛ばす、SaaS開発者にとって最悪のバグだ。

※この記事は、Claude Codeで1人開発しているSNS運用SaaS「ThreadPost」の開発日記です。

SNS運用を自動化しませんか?

ThreadPostなら、投稿作成・画像生成・スケジュール管理までAIがサポート。

無料で始める

核心回答:サーバーレスのタイムアウトが二重投稿を生む

投稿システムの安定化をやっていた。結果として、システムを根底から作り直す羽目になった。

リトライによる二重投稿の悪夢と、システム安定化への戦い
リトライによる二重投稿の悪夢と、システム安定化への戦い

今週の総コミットは29件。新機能1件に対して、バグ修正が3件。

InngestVercelの組み合わせは強力だ。でも、ネットワークの瞬断やタイムアウトが起きた瞬間に牙を剥く。

「完了したのに完了通知が届かない」というサーバーレスの呪い。これと3日間戦い続けた。

サーバーレスアーキテクチャは、スケーラビリティに優れている。アクセスが急増しても自動でリソースが割り当てられる。

しかし、その代償として「状態の管理」が極めて難しくなる。プロセスはいつ強制終了されるかわからない。

APIへの送信は完了したのに、DBへの「完了済み」フラグの書き込みがタイムアウトする。

次にリトライが走った時、システムは「まだ送信されていない」と勘違いする。

結果、同じ内容がもう一度SNSに放たれる。スパムボットの完成だ。

これを防ぐため、DB再チェック、24時間検出窓、投稿時間ガードの3層構造を実装した。

一般的なWebアプリケーションなら、DBのユニーク制約だけで十分なことが多い。

しかし、外部APIと連携する非同期処理では、それだけでは防ぎきれない。

ネットワークの向こう側で何が起きているか、完全には把握できないからだ。

だからこそ、複数のガードを重ねて、異常な状態を検知して止める仕組みが必要になる。

分散システムにおける状態管理の難しさは、大手テック企業でも頻繁に障害を引き起こす。

それを1人開発のSaaSで、しかもAIの力を借りて解決しなければならない。

AIにリトライを頼んだらスパムボットが生まれた

今週の開発実績:システムの根底からの作り直し
今週の開発実績:システムの根底からの作り直し

Claude Codeに「失敗したら再実行して」と指示した。

テスト環境でリトライボタンを押した。SNSに同じ投稿が2回流れた。

「再実行」のコードだけが追加され、重複チェックのロジックは一切なかった。

分散システムにおける「Exactly-once(正確に1回)」の保証。業界では聖杯と呼ばれる難題だ。

AWS SQSGoogle Cloud Pub/Subを使っても、デフォルトは「At-least-once(少なくとも1回)」。

つまり、何もしなければ重複は必ず起きる。だからシステム側で冪等性(べきとうせい)の担保が必要になる。

僕はAIがよしなに冪等性を担保してくれるはずだと高を括っていた。

結果として、エンジニアにとっての悪夢が現実になった。

サーバーレス環境のInngestVercelでは、この問題がさらに複雑になる。

Vercelの実行時間制限でプロセスが強制終了されると、タスクは中途半端な状態になる。

APIへの送信は完了したのに、DBへの「完了済み」フラグの書き込みがタイムアウトする。

次にリトライが走った時、システムは「まだ送信されていない」と勘違いする。

結果、同じ内容がもう一度SNSに放たれる。

しんたろーしんたろー:
タイムラインに並んだ2つの同じ投稿を見た瞬間、変な汗が出た。
企業アカウントでこれが起きたら、一発で契約解除だ。炎上の火種にしかならない。

ここから泥臭い戦いが始まった。一般的なAPI開発なら、DBのユニーク制約という1層のガードで済ませることが多い。

でも、それじゃ防げなかった。「fix: リトライ時の重複投稿防止 — DB再チェック+24h検出窓+posted_atガード」を実装した。

まず、処理の冒頭でDBから最新のステータスを再チェックする。キャッシュをバイパスして直接見に行く。

次に、24時間以内の投稿を検出する窓を作った。Inngestの指数バックオフ対応のため、5分から24時間に拡大した。

最後に、「posted_at」でガードをかけた。X投稿は「posted_at」を先行設定する仕様のため、これでスキップ判定を行う。

これで3層。1個のガードが抜けたとき、次のガードが止める。

「fix: next_execution_atの分・秒を切り捨てて正時にする」という修正も入れた。

cronは毎時0分に実行されるが、秒単位の端数が残っていると数秒の差で取りこぼす。

1時間遅れで送信されるという地味なバグ。これも重複や送信漏れの温床だった。

時刻の扱いは、プログラミングにおいて常に鬼門だ。

特に分散システムでは、サーバー間のわずかな時刻のズレが致命的なバグを引き起こす。

「fix: 日常投稿の画像自動生成をawaitに変更(Vercelで打ち切られる問題)」も追加した。

「fire-and-forget」だとVercelのサーバーレス環境でレスポンス返却後に処理が打ち切られる。

Inngestイベントが送信されず、システムが宙吊りになる。これもリトライの暴走を招く。

非同期処理の完了を待たずにレスポンスを返すのは、パフォーマンス向上の常套手段だ。

しかし、サーバーレス環境ではそれが命取りになる。プロセス自体が凍結されるからだ。

さらに「fix: JSON文字列内のリテラル改行でパース失敗するバグを修正」も絡んでいた。

JSON内に生改行が入ってパースエラー。これが無駄なリトライを引き起こす原因の一つだった。

細かいエラーが積み重なって、Inngestのリトライキューにタスクが溜まっていく。

データの境界線が曖昧になると、システムは簡単に暴走する。

重複率100%から0%へ。3層のガードを作って、ようやく「同じ投稿が2回出ない」状態になった。

30分で終わった。ただしコードはガード句だらけでボロボロだった。

触るな危険のダイアログと泥臭いDOMの戦い

投稿作成ダイアログで、直感的にテキストを入力したかった。

「feat: 日常投稿機能の実装(Phase 1a/1b/2: DB・型・サービス層・API・UI)」の続きだ。

ペルソナに日常投稿の自動生成機能を追加していた。手動でサクッとテキストを修正できる、気持ちのいいUIを目指した。

textareaをクリックした。キーボードが即座に閉じた。

何度タップしても、一瞬でフォーカスが外れる。入力成功率0%。

「触るな危険」状態のダイアログが爆誕した。

ReactなどのUIフレームワークでよくある、DOMイベント伝播の罠だ。

親要素の背景クリックで「ダイアログを閉じる」処理を入れるのは、モダンUIの基本。

でも、子要素からのイベントバブリングを止め忘れると、悲惨なことになる。

入力欄を触ったつもりが、親要素に「閉じろ」という命令が伝わってしまう。

ユーザー体験を向上させるための機能が、逆にユーザーを激怒させるバグに変わる瞬間だ。

「fix: 投稿作成ダイアログでテキスト入力できない問題を修正」をコミットした。

クリック対象が「input」「textarea」「select」の場合は早期リターンする。単純なガード句を一つ入れるだけ。

たったこれだけのことで、入力成功率は100%になった。

複雑なダイアログを作っていると、こういう基礎的なDOMの挙動を見落としがちだ。

AIにUIコンポーネントの生成を任せると、見た目は完璧なものが一瞬で出来上がる。

しかし、イベントの伝播やフォーカス管理といった、目に見えない部分の考慮がすっぽり抜け落ちることがある。

しんたろーしんたろー:
基礎的なイベント伝播のバグ。初心者がやるようなミスだ。
派手なAI機能の裏には、こういう地味なDOMとの格闘が100個くらいある。

「fix: 予約時間変更が全アカウント分更新されないバグを修正」も同じような泥臭い修正だ。

idで1件のみ更新していたため、複数アカウントへの予約が片方にしか反映されなかった。

配列を使って全アカウント分を一括更新するよう修正した。

状態管理の不整合は、マルチテナントSaaSにおいて最も恐ろしいバグの一つだ。

AさんのデータがBさんに見えてしまうような、致命的な情報漏洩に繋がる危険性すらある。

「fix: 投稿作成ダイアログのscheduled_postsをupsertに変更」もやった。

「insert」だと日時変更時に既存レコードが更新されず、重複レコードが作成されていた。

ここでも「重複」との戦いがあった。一つずつ潰していくしかない。

データベースの操作において、「作成」と「更新」を厳密に分けるのは意外と難しい。

「upsert」を使うことで、レコードが存在すれば更新、なければ作成という処理を安全に行える。

「fix: AI生成画像がSNSフィルタで弾かれてP1に紐づかないバグを修正」も追加した。

AI生成画像は自前で生成したものだから、SNSフィルタを通す必要がない。

1枚目を自動的に選択状態にするよう変更した。

外部APIの仕様変更や、予期せぬフィルタリングによって、システムは簡単に壊れる。

こういう泥臭い修正の積み重ねが、システムに安定をもたらす。

僕は二重投稿の恐怖から解放され、ようやく安眠を手に入れた。

ここまで読んだあなたに

今なら無料で全機能をお試しいただけます。設定後はAIが投稿案を毎日生成。確認して選ぶだけ。

無料で始める

落とし穴:スレッド投稿がXに流し込まれる

AIに「スレッド投稿を実装して」と頼んだ。

「feat: スレッド自動生成 + 編集UI + publish対応」というコミットだ。

Threads APIX APIの仕様差を無視して、Threads用のコードがXに流し込まれた。

全投稿がエラーで弾かれた。

「fix: 投稿作成ダイアログのplatformがthreadsにハードコードされていたバグを修正」で直した。

Xアカウントの予約投稿もThreads APIで実行されて失敗していた。

「スレッド」という単語一つで、プラットフォームの判定ロジックがすべて「threads」に書き換わっていた。

しんたろーしんたろー:
まじかよ…全部Threads行きになってる。
XのAPIキーでThreadsのエンドポイント叩いてるんだから、そりゃ動くわけない。

プラットフォームごとにAPIの仕様は全く異なる。

Xは文字数制限が厳しく、メディアの添付方法も独特だ。

一方、Threadsは比較的緩いが、スレッドの構築方法が違う。

AIは「スレッド」という言葉の響きだけで、すべての処理をThreads向けに最適化してしまった。

文脈の境界線を勝手に越えてしまう。これがAI開発の恐ろしいところだ。

人間なら「Xのスレッド機能」と「ThreadsというSNS」を文脈で区別できる。

しかし、AIにとっては単なるトークンの連続に過ぎない。

だからこそ、プロンプトで指示を出す際には、人間相手以上に厳密な言葉の定義が必要になる。

今日の数字

| 項目 | 数字 | 比較・備考 |

|---|---|---|

| 総コミット | 29件 | 先週は15件。倍近い修正量。 |

| 新機能 | 1件 | 企業開発なら1週間。僕は1日。 |

| バグ修正 | 3件 | そのうち1件が致命傷になりかけた。 |

| 重複防止層 | 3層 | 一般的なAPI開発では1層。 |

1回の投稿失敗が招くSNS上の信頼損失は、開発工数100時間分に匹敵する。

ユーザーからの「また重複してるよ」という問い合わせ対応コストは、プライスレスだ。

僕は数時間の格闘で、未来の炎上を未然に防いだ。

よくある質問

Q: サーバーレス環境でのタイムアウトによるコスト増加はどう防ぐ?

実行時間の制限を厳密に設定し、再試行回数に上限を設ける。今回は24時間の検出窓を設けることで、無駄なリトライループを物理的に遮断した。無限リトライはAPI課金を爆発させる原因になる。

Q: 複数プラットフォームのAPIを扱う際、AIはどうコードを生成した?

APIのエンドポイントやペイロードの構造が違うのに、AIは共通の型定義でまとめようとした。結果としてXの投稿がThreadsの仕様で送信されて全滅した。型定義の段階で厳密に分離しないと、プラットフォーム間の境界線が簡単に崩れる。

Q: 冪等性を担保するためのDB設計はどう実装した?

単一のユニークキーに頼らず、複合的な条件で状態を管理した。今回はステータス確認とタイムスタンプを組み合わせた。一つの制約がすり抜けられても、別のカラムで異常を検知できる構造にした。

泥臭いガードの先に安眠がある

二重投稿の悪夢から解放され、ようやく安心してシステムを動かせるようになった。

👉 ThreadPostでSNS運用を自動化する

ThreadPost — SNS投稿をAIが自動化

この記事が参考になったら、ThreadPostを試してみませんか?投稿作成・画像生成・スケジュール管理まで、AIがサポートします。

無料で始める

この記事をシェア

XはてブLINE
しんたろー

ThreadPost開発者・個人開発エンジニア

AI × SaaS個人開発者。Cursor / Claude Code を使った効率的開発、SNS自動化について実体験から発信。

人気の記事