「トラック来てるんだけど」— Claude Codeで作った業務アプリが本番で壊れて、Claude Codeで15分で直した話
はじめに
Claude Codeで業務アプリを作っている非エンジニアです。建設会社で現場代理人をやりながら、自社向けに樹木管理アプリ・日報アプリ・CRMを開発・運用しています。
この記事は、Claude Codeで作ったシステムが出荷当日の朝に壊れて、Claude Codeで15分で復旧した実話です。
「AIで作ったシステムって本番で大丈夫なの?」という疑問への、一つの答えになれば。
技術スタック
- Next.js (App Router) + TypeScript
- Supabase (PostgreSQL + Auth + Storage)
- Vercel (本番ホスティング)
- PWA (オフラインファースト設計)
- 開発: Claude Code
朝7時半、電話が鳴る
「QR読めないんだけど。全部!! トラックもう来てるんだけど。」
「え??」 滝汗、、、
391本の樹木を取引先に出荷する日だった。トラックはもう敷地に入っている。
うちの樹木管理アプリでは、1本ずつ木にQRラベルを貼り、出荷時にスキャンして照合する仕組みになっている。それが全部「出荷対象ではありません」と返ってくる。
応急処置: 15分でデプロイ
パニックになりかけたが、やることは明確だった。
「QRが使えないなら、管理番号で検索してタップでピッキングできるようにすればいい。」
Claude Codeを開いて、こう伝えた:
ピッキング画面に手入力モードを追加してほしい。
管理番号を入力して検索 → 該当する樹木が表示される → タップで照合完了。
QRスキャンの代替手段として、既存のピッキング画面に組み込む形で。
Claude Codeが出してきたコードは、既存の PickingView.tsx に検索フォームとフィルタロジックを追加するもの。差分を確認して、ビルドして、git push。Vercelが自動デプロイ。
電話を切ってから15分後、現場で動作確認。積み込み開始。
原因調査: SQLで追跡
トラックが出発した後、原因を調べた。これもClaude Codeと一緒に。
QRスキャンで「出荷対象ではありません」と出る。
QRコードの中身はUUID。DBのtreesテーブルにそのUUIDが存在しない。
でもチェックリスト上にはその木が表示されている。
activity_logsとtreesテーブルを突き合わせて、原因を特定したい。
Claude Codeが生成したSQLを実行して、すぐ分かった。
-- 出荷対象の樹木の作成日を確認
SELECT id, management_number, species, created_at
FROM trees
WHERE shipment_id = 'xxx'
ORDER BY created_at;
-- → 全部 3/17〜18 に作成されている
数日前、単価を修正するために樹木データを削除→再登録していた。再登録で新しいUUIDが発番されたが、木に貼ってあるQRラベルには古いUUIDが印刷されたまま。
QRラベル → UUID_old → DBに存在しない(削除済み)
DB上の木 → UUID_new → QRラベルと紐づかない
「編集」で済むことを「削除→再作成」でやったのが原因だった。
再発防止: 「削除」を構造的に封じる
原因が分かったので、同じ事故が起きない設計に変えた。
1. 「無効」ステータスの新設
// Before: 物理削除
await supabase.from('trees').delete().eq('id', treeId);
// After: 論理削除(ステータス変更)
await supabase
.from('trees')
.update({ status: 'invalid' })
.eq('id', treeId);
削除ボタンを「無効化」に差し替えた。UUIDは生き続ける。QRラベルとの紐づけも壊れない。復帰も可能。
2. QR照合のフォールバック
// UUIDで見つからない場合、management_numberでフォールバック
let tree = await findByUUID(scannedValue);
if (!tree) {
tree = await findByManagementNumber(scannedValue);
}
QRの中身がUUIDでもmanagement_numberでも照合できるようにした。ラベルの再印刷なしで旧ラベルにも対応。
3. 手入力ピッキング(恒久化)
応急処置で作った手入力モードは、そのまま正式機能として残した。QRスキャンが使えない状況は今後も起き得る(ラベル汚損、カメラ故障、etc.)。代替手段は保険として常に必要。
1日で追加した機能(結果的に8つ)
応急処置から始まって、その日のうちに以下を追加・デプロイした:
| # | 機能 | 目的 |
|---|---|---|
| 1 | 手入力ピッキング | QR代替 |
| 2 | 既存出荷に明細追加 | 現場判断の追加積み込み対応 |
| 3 | 一括取消(3重確認付き) | 積まなかった木の除外 |
| 4 | 一括手動確認 | 未スキャン分の事後処理 |
| 5 | クライアント紐づけ解除 | 出荷取消後のデータ整合 |
| 6 | ポータル手入力受入 | 取引先側もQR不要に |
| 7 | 「無効」ステータス | 削除の代替 |
| 8 | QR照合フォールバック | UUID + 管理番号の両方で照合 |
全部Claude Codeとの対話で実装した。1人で。1日で。
非エンジニアがClaude Codeで本番運用して分かったこと
「AIで作ると壊れる」は半分正しい
壊れた。実際に壊れた。391本の出荷が止まりかけた。
でもそれは「AIで作ったから壊れた」んじゃない。「削除と編集の違いを理解せずに運用した」から壊れた。人間がやっても同じミスはする。
「AIで作ると直せる」も同じくらい正しい
もしこれが外注で作ったシステムだったら?
- 朝7時半に電話する
- 担当者に繋がる(繋がるか?)
- 状況を説明する
- 見積もりが出る
- 修正される
トラックは待ってくれない。
Claude Codeで作ったものは、Claude Codeで直せる。自分で作ったものは、自分で直せる。これが内製の最大のメリットだった。
大事なのは「壊れないシステム」じゃない
完璧なシステムは存在しない。大事なのは壊れた時に止まらないこと。
- QRが使えない → 手入力で回る
- 削除してしまった → 無効化で代替する(そもそも削除できなくする)
- 現場判断で追加が発生 → 既存出荷に明細追加で対応する
イレギュラーは必ず起きる。そこからの復旧速度が、システムの本当の価値。
まとめ
| 項目 | 内容 |
|---|---|
| 障害内容 | QRスキャン全滅(UUID不一致) |
| 原因 | 単価修正のための削除→再登録でUUIDが変わった |
| 復旧時間 | 15分 |
| 復旧方法 | Claude Codeで手入力ピッキング機能を追加→Vercelデプロイ |
| 再発防止 | 物理削除→論理削除(無効ステータス)に変更 |
| 追加機能 | 計8機能を当日中にデプロイ |
| 開発者 | 非エンジニア1人 + Claude Code |
391本、全部トラックに積めました。
建設会社で働きながら、Claude Codeで業務アプリを作っています。
日報アプリ・CRM・樹木管理アプリの実画面 → GenbaLink ポートフォリオ