AIにはセッション間の記憶がないため、前のセッションで修正したエラーも、新しいセッションでは「初めて見る問題」として扱われる。 同じ調査を繰り返し、同じ結論にたどり着き、同じ修正を適用する。 本記事では、この「記憶と忘却」の問題を構造的に分析し、エラーを「セッションの修正」から「アーキテクチャの改善」へ改善した記録を共有しています。
対象読者
- AIと協働するなかでエラーの再発に悩んでいる人
- AI駆動開発における知識の永続化に関心がある人
再発が観測されたエラーパターン
パターン1:外部メトリクス取得の連続失敗
1初回発生:2 外部メトリクスAPIからのデータ取得が3日連続で失敗3 → 原因: 外部データプロバイダAPIの無料枠レート制限(1分5回)4 → 修正: タイムアウトを30秒→60秒に変更5
6再発:7 数週間後に同じタイムアウトエラー8 → AIは「初めて見る問題」として調査を開始9 → 同じ結論にたどり着く10 → 同じ修正を提案パターン2:pre-commit hookの権限リセット
1初回発生:2 git commit 時に pre-commit hook の実行権限エラー3 → 原因: chmod +x が適用されていない4 → 修正: chmod +x .git/hooks/pre-commit5
6再発:7 git操作の後に権限がリセットされる8 → AIは再び chmod +x を提案9 → 根本原因(hook管理の仕組み自体)に触れないパターン3:外部サービス認証エラー
1初回発生:2 外部APIの認証トークンが期限切れ3 → 原因: トークンの自動更新が未実装4 → 修正: 手動でトークンを再取得5
6再発:7 トークンが再び期限切れ8 → AIは「トークンを再取得してください」と提案9 → 自動更新の仕組みは提案しない(前回の文脈がないため)パターン4:PYTHONPATHの設定漏れ
1初回発生:2 launchd経由でスクリプト実行時に ModuleNotFoundError3 → 原因: plistの EnvironmentVariables に PYTHONPATH 未設定4 → 修正: 該当plistに PYTHONPATH を追加5
6再発:7 別のplistファイルで同じエラー8 → AIは同じ修正を提案9 → 「全plistを一括チェック」は提案しないなぜ修正したはずのエラーが再発するのか
原因1:修正がセッションローカル
1セッション内の修正は「短期記憶」だ。2
3セッションA:4 外部メトリクスAPIのタイムアウトを60秒に変更 ← 修正完了5 CLAUDE.mdには追記しなかった ← ここが問題6
7セッションB(翌週):8 外部メトリクスAPIのタイムアウトエラー発生9 AIは60秒に変更した経緯を知らない10 → 最初から調査をやり直す原因2:修正がファイルローカル
1plist_A.plist に PYTHONPATH を追加 ← 修正完了2plist_B.plist にも同じ問題がある ← 未修正3plist_C.plist にも同じ問題がある ← 未修正4
5ファイル単位の修正は「同じパターンの別ファイル」に波及しない。6AIは「今見ているファイル」を修正するが、7「同じ問題を持つ他のファイル」を探索しない。原因3:修正が不完全(症状の対症療法)
1# 対症療法(症状を消す)2response = requests.get(url, timeout=60) # タイムアウトを延ばす3
4# 根本対策(原因を潰す)5# → レート制限を意識した呼び出し間隔の制御6# → フォールバック先の追加7# → キャッシュの導入原因4:知識の伝播漏れ
1本番環境: 修正済み2Pre環境: 未修正 ← 同じエラーが出る3開発環境: 未修正 ← 同じエラーが出る4
51つの環境での修正が他の環境に伝播しない。忘却のスペクトラム:記憶の強さの5段階
1記憶の強さ(弱→強):2
3Level 1: セッション記憶4 → セッション終了で消失5 → 最も弱い記憶6 → 例: 「外部メトリクスのタイムアウトを60秒にした」7
8Level 2: CLAUDE.md記憶9 → セッションをまたいで保持10 → AIがCLAUDE.mdを読めば思い出す11 → 例: 「よくあるエラー表に追記」12
13Level 3: コードレベルの修正14 → ファイルに永続化される15 → ただし同種の問題を別ファイルでは防げない11 collapsed lines
16 → 例: 「timeout=60をコードに直接書く」17
18Level 4: テストレベルの修正19 → 自動で回帰を検出する20 → CIで毎回実行される21 → 例: 「タイムアウトのフォールバック動作をテスト」22
23Level 5: アーキテクチャレベルの修正24 → 問題のカテゴリ全体を不可能にする25 → 最も強い記憶26 → 例: 「フォールバックチェーン実装で取得失敗を撲滅」セッション修正をアーキテクチャ改善に引き上げる
具体例:メトリクス取得エラーの進化
1# Level 1: セッション修正(次のセッションで忘れる)2# 「外部メトリクスAPIのタイムアウトを60秒に変更」3response = requests.get(url, timeout=60)1# Level 2: CLAUDE.md修正(AIが読めば思い出す)2# よくあるエラー表に追記:3
4| エラー | 原因 | 対処法 |5| ------------------ | ----------------------------------------------- | ---------------------------------- |6| メトリクス取得失敗 | 外部データプロバイダAPIの無料枠制限。1分5回まで | タイムアウト60秒を確認。頻度を制限 |1# Level 3: コードレベルの修正(ファイルに永続化)2METRICS_API_TIMEOUT_SECONDS = 60 # 外部データプロバイダ無料枠対応3METRICS_API_MAX_CALLS_PER_MINUTE = 54
5response = requests.get(url, timeout=METRICS_API_TIMEOUT_SECONDS)1# Level 4: テストレベルの修正(回帰を自動検出)2from unittest import mock3from requests.exceptions import Timeout4
5def test_metrics_provider_handles_timeout():6 """タイムアウト時にフォールバック値を返すことを検証."""7 provider = MetricsProvider()8 with mock.patch("requests.get", side_effect=Timeout):9 result = provider.get_metrics()10 assert result == provider.DEFAULT_METRIC # フォールバック値を返す11
12def test_metrics_provider_respects_rate_limit():13 """レート制限を超えない呼び出し間隔を検証."""14 provider = MetricsProvider()15 call_times = []12 collapsed lines
16 original_get = requests.get17
18 def tracking_get(*args, **kwargs):19 call_times.append(time.time())20 return original_get(*args, **kwargs)21
22 with mock.patch("requests.get", side_effect=tracking_get):23 for _ in range(6):24 provider.get_metrics()25
26 # 6回の呼び出しが1分以上に分散されていること27 assert call_times[-1] - call_times[0] >= 601# Level 5: アーキテクチャ修正(問題カテゴリの撲滅)2# MetricsProviderにフォールバックチェーンを実装3# 外部データプロバイダ → キャッシュ値 → デフォルト値4
5def get_metrics(self) -> float:6 """外部メトリクスを取得する。複数のフォールバックで取得失敗を防ぐ."""7 # 1st: プライマリソース8 try:9 metrics = self._fetch_from_provider()10 self._update_cache(metrics)11 return metrics12 except (Timeout, ConnectionError, RateLimitError) as e:13 logger.warning(f"[Metrics] プライマリ取得失敗: {e}")14
15 # 2nd: キャッシュ値(最終取得成功値)8 collapsed lines
16 cached = self._get_cached_metrics()17 if cached is not None and self._is_cache_fresh(cached):18 logger.info(f"[Metrics] キャッシュ値を使用: {cached.value}")19 return cached.value20
21 # 3rd: デフォルト値22 logger.warning(f"[Metrics] デフォルト値を使用: {self.DEFAULT_METRIC}")23 return self.DEFAULT_METRICLevel 5まで引き上げると、メトリクス取得の失敗はシステムの動作に影響しなくなる。個別のエラーに対処するのではなく、「外部API呼び出しは失敗する前提」でアーキテクチャを設計する。
CLAUDE.mdの「よくあるエラー」表
再発するエラーに対応するたびに、CLAUDE.mdに知見を蓄積してきた。現在8件以上のエントリがある。
1## よくあるエラーと対処法2
3| エラー | 原因 | 対処法 |4| ----------------------------- | ----------------------------------- | ----------------------------------------------- |5| メトリクス取得失敗 | 外部データプロバイダAPIの無料枠制限 | タイムアウト60秒、フォールバックチェーン確認 |6| ModuleNotFoundError (launchd) | plistのPYTHONPATH未設定 | EnvironmentVariablesにPYTHONPATH追加 |7| 外部サービス認証エラー | トークン期限切れ | トークン再取得。自動更新の仕組みを確認 |8| pre-commit hook 権限エラー | chmod +x 未適用 | chmod +x .git/hooks/pre-commit |9| Parquetスキーマ不一致 | カラム追加後の旧ファイルとの混在 | マイグレーションスクリプト実行 |10| Slack通知失敗 | Webhook URL未設定 or 期限切れ | .envのSLACK_WEBHOOK_URLを確認 |11| DB接続タイムアウト | TimescaleDBのconnection pool枯渇 | max_connectionsの設定確認、コネクション漏れ調査 |12| pytest収集エラー | conftest.pyの循環import | conftest.pyのimport構造を確認 |このテーブルが機能する理由
1AIの動作フロー:2 1. セッション開始 → CLAUDE.mdを読み込み3 2. 「よくあるエラー」テーブルを認識4 3. ユーザーからエラー報告を受ける5 4. テーブルと照合 → 既知のエラーなら即座に対処法を提示6
7テーブルがない場合:8 1. セッション開始9 2. ユーザーからエラー報告を受ける10 3. ゼロから調査を開始11 4. 30分かけて同じ結論にたどり着く再発防止の階層的アプローチ
修正後のチェックリスト
エラーを修正した後、以下を確認する。
1## エラー修正後チェックリスト2
31. [ ] CLAUDE.mdの「よくあるエラー」表を更新したか?4 → 次のセッションでAIが同じ問題をすぐ対処できるように5
62. [ ] 同じパターンが他のファイルにないか確認したか?7 → grep/find で同種の問題を探索8
93. [ ] テストを追加したか?10 → このエラーが再発した場合にCIで検出できるように11
124. [ ] アーキテクチャレベルの改善は必要か?13 → 個別対応ではなく、問題カテゴリ全体を防ぐ設計変更実際の適用例:PYTHONPATH問題
1# Level 2: CLAUDE.mdに追記2# 「plist作成時は必ずPYTHONPATHをEnvironmentVariablesに設定」3
4# Level 3: 全plistを一括チェック5grep -rL "PYTHONPATH" ~/Library/LaunchAgents/com.project.*.plist6# → PYTHONPATH未設定のplistを一覧表示7
8# Level 4: テスト追加9# plistのバリデーションテストで必須環境変数をチェック1def test_all_plists_have_required_env_vars():2 """全plistファイルに必須の環境変数が設定されていることを検証."""3 required_vars = ["PROJECT_ROOT", "PYTHONPATH"]4 plist_dir = Path.home() / "Library" / "LaunchAgents"5 plists = list(plist_dir.glob("com.project.*.plist"))6
7 for plist_path in plists:8 with open(plist_path, "rb") as f:9 plist_data = plistlib.load(f)10
11 env_vars = plist_data.get("EnvironmentVariables", {})12 for var in required_vars:13 assert var in env_vars, (14 f"{plist_path.name} に {var} が未設定"15 )1# Level 5: アーキテクチャ改善2# plist生成スクリプトにテンプレートを導入し、3# 必須環境変数を自動設定4def generate_plist(job_name: str, script_path: str) -> dict:5 """plistを生成する。必須環境変数は自動設定."""6 return {7 "Label": f"com.project.{job_name}",8 "ProgramArguments": ["/usr/bin/python3", script_path],9 "EnvironmentVariables": {10 "PROJECT_ROOT": str(PROJECT_ROOT),11 "PYTHONPATH": str(PROJECT_ROOT),12 # 手動設定を不要にすることで設定漏れを防ぐ13 },14 }「忘却」を前提とした設計
AIの記憶モデル
1人間の記憶:2 短期記憶 → 反復 → 長期記憶 → 忘却曲線で徐々に薄れる3
4AIの記憶:5 セッション記憶 → セッション終了 → 完全消失6 CLAUDE.md → 永続 → ただし読まれないと意味がない7 コード → 永続 → ただし「なぜ」は残らない8 テスト → 永続 + 自動検証 → 最も信頼できる記憶設計指針
1memory_design_principles = {2 "assume_forgetting": "AIは忘れる前提で設計する",3 "externalize_decisions": "意思決定は外部ファイルに永続化する",4 "automate_verification": "記憶に頼らずテストで検証する",5 "escalate_fixes": "セッション修正をアーキテクチャ改善に引き上げる",6}学んだこと
1. セッション内の修正は最も弱い記憶。CLAUDE.md、テスト、アーキテクチャの順に記憶が強くなる
セッション修正は翌日には消える。CLAUDE.mdに書けばAIが読む限り残る。テストに落とせばCIが自動で検証する。アーキテクチャを変えれば問題自体が発生しなくなる。修正を「どのレベルの記憶にするか」を意識的に判断する。
2. 「同じエラーが再発した」はCLAUDE.mdの更新漏れのサイン
同じエラーが2回出たら、それはCLAUDE.mdに追記すべきだったサインだ。修正後にCLAUDE.mdを更新するルールを設けることで、2回目の発生時にはAIが即座に対処できるようになる。修正そのものより「修正の記録」が重要。
3. AIの「よくあるエラー」表は、人間のFAQと同じ——頻出問題を事前に教えることでセッション品質が上がる
人間の新人にFAQを渡すように、AIにも「よくあるエラー」表を渡す。8件のエントリは少なく見えるが、これだけでセッション冒頭の30分の調査時間を節約できる。エラー対応の知見は蓄積すればするほど、AIの初動が速くなる。
まとめ
「以前対応したエラーがまた出現」問題への対策は以下の3点だ。
- エラー修正を5段階の記憶レベルで管理する: セッション修正(最弱)→ CLAUDE.md追記 → コード修正 → テスト追加 → アーキテクチャ改善(最強)。再発頻度と影響度に応じて適切なレベルを選ぶ
- CLAUDE.mdの「よくあるエラー」表を運用する: エラーを修正するたびにテーブルに追記する。AIは次のセッション開始時にこのテーブルを読み込み、既知の問題に即座に対応できる
- 「忘れる前提」で設計する: AIはセッション間の記憶を持たない。これはバグではなく仕様だ。外部ファイルへの永続化、テストによる自動検証、アーキテクチャによる問題撲滅——記憶に頼らない仕組みを設計する