最終更新: 2026/06/22 18:56
実装失敗パターン一覧
AIエージェント(Claude Code / Gemini 等)による実装で発生したエラー・不整合と、その根本原因・対策をまとめたページです。同じ失敗を繰り返さないためのナレッジベースとして活用してください。
分類
| カテゴリ | 説明 |
|---|---|
| 計算ロジック | フィルタ選択ミス、ゼロ除算、符号反転等 |
| データ不整合 | DDLコード値 vs 実データの乖離、列ずれ等 |
| スコープ・変数 | 変数の参照スコープ外、未定義関数 |
| テスト回帰 | 新機能追加後にテストが壊れる |
| Git 運用 | コンフリクト解消ミス、並行開発の干渉 |
| ドキュメント | _config.json 未登録、ステータス更新漏れ |
| 仕様書記述 | 既存コード未確認で書いた仕様書に固有名詞・構造の誤り |
| 数式設計 | GAS + Sheets 数式の組合せ・データ型自動変換・Unicode 等の落とし穴 |
全パターン一覧
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 1 | 04-15 | 計算ロジック | filterWithRecalcTotal で非加算指標の Total が壊れた | MAS-024 | 仕様書にフィルタ選択基準がなかった | 出力行ごとに「加算可能か」を明記。非加算指標は filterValues + Total独自計算 |
| 2 | 04-15 | 計算ロジック | 売上ゼロ月で BEP が固定費と同額になった | MAS-024 | ゼロ除算時のフォールバック(varRate=0)が誤り | エッジケーステーブルで異常系の表示値を事前定義 |
| 3 | 04-15 | データ不整合 | 固変区分の判定が FV_VAR で不一致 | MAS-024 | DDL定義のコード値と実データの日本語表記が異なっていた | 実行前タスクに「MCP で実データを確認」を追加 |
| 4 | 04-15 | スコープ・変数 | planCtx is not defined | MAS-001 | 計画パイプラインの if ブロック外に配置した | 変数のスコープを仕様書に明記 |
| 5 | 04-15 | Git 運用 | .claspignore の否定パターンで worktree が混入 | MAS-096 | .claude/ ディレクトリが除外対象に含まれていなかった | clasp status で Tracked ファイルを検証 |
| 6 | 04-15 | Git 運用 | コンフリクト解消で追加日時列が消失 | MAS-001 | git checkout --theirs で古い版を採用した | コンフリクト解消後に列構造の差分を確認 |
| 7 | 04-16 | スコープ・変数 | MAS-192 ファイル分割後に旧ヘルパー関数が未定義 | MAS-192 | 分割時に旧ファイルを削除したが旧関数呼び出しが残存 | 互換レイヤーを追加。分割時は grep で旧関数の参照を全件確認 |
| 8 | 04-16 | テスト回帰 | テスト T4-03 で BEP セクションが未登録科目として検出 | MAS-024 | 新セクションの行ラベルがテストの SKIP_PATTERNS に未登録 | 新セクション追加時はテストの SKIP_PATTERNS も同時更新 |
| 9 | 04-16 | テスト回帰 | テスト T4-21 で YoY 差異列追加による最終月列のずれ | MAS-020 | テストが headers.length - 1 で最終月を固定参照 | △列や空白列をスキップして実データ列を特定する |
| 10 | 04-16 | Git 運用 | TODO_future.md のサマリー・完了一覧が消失 | — | 複数ワークスペースでの並行開発でコンフリクト解消時に上書き | ファイルの担当を分ける。TODO_future.md は片方だけが触る |
| 11 | 04-16 | ドキュメント | _config.json に新規仕様書が未登録 | MAS-073 等 | 仕様書作成時に _config.json への登録を忘れた | 汎用プロンプトの Phase 3 に登録ステップを必須化 |
| 12 | 04-16 | データ不整合 | 銀行CSV消込が実行されない | MAS-145 | マッチ決済ID の形式が stlRowMap のキーと不一致の可能性 | 消込実行前にID形式を検証するガードを追加 |
教訓サマリー
仕様書で防げたもの(#1, #2, #3, #4)
- エッジケーステーブル: ゼロ除算、比率異常値、マスタ未設定のフォールバックを事前定義
- フィルタ選択基準: 加算可能/非加算の区別を明記
- 実データ検証: MCP で実データとDDLコードの乖離を事前チェック
- 変数スコープ: if ブロック内/外の配置を明記
マッチングロジックで防げたもの(#13-#17)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 13 | 04-16 | 計算ロジック | 合算マッチが単一候補提案(Pass 3)に先取りされた | MAS-162 | Pass 3 を Pass 2 より先に実行していた | 金額完全一致の合算を単一候補提案より先に実行(Pass 2 → Pass 3 の順) |
| 14 | 04-16 | 計算ロジック | 全STL合計で合算を試みたが候補が多すぎて不一致 | MAS-162 | 異なる取引先のSTLも含めて合計していた | 同一取引先でグルーピングしてから部分集合を探索 |
| 15 | 04-16 | 計算ロジック | 同一取引先4件中3件の合算が検出されない | MAS-162 | 全件合計のみ照合し部分集合を試行しなかった | 貪欲法(日付昇順に加算)+ 同一金額N件の2方式を追加 |
| 16 | 04-16 | 計算ロジック | 合算マッチで同じSTLが次の行でも再利用された | MAS-162 | 合算成功時に candidates[ci].matched=true を設定していなかった | 合算マッチ成功時にSTLをロック(matched=true) |
| 17 | 04-16 | 計算ロジック | Date オブジェクトの文字列ソートで日付順が壊れた | MAS-162 | String(dueDate) がタイムゾーン付き長文字列になった | Utils.parseDateToYm() で YYYY-MM 形式に統一してからソート |
テスト設計で防げたもの(#8, #9)
- 新機能追加時は テストの固定値参照を見直す(列数、行ラベル等)
- SKIP_PATTERNS や列位置の取得は 動的に 行う
Git 運用で防げたもの(#5, #6, #10)
- 並行開発時は ファイルの担当を分ける
- コンフリクト解消後は 差分の全体像を確認してからコミット
git checkout --theirsは安易に使わない
プロセスで防げたもの(#7, #11, #12)
- ファイル分割時は grep で旧関数名の全参照を確認
- 新規 .md 作成時は _config.json への登録を必須チェック
- ID マッチング時は キーの形式(STL_ vs INV_)を検証
数式設計で防げたもの(#21-#24)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 21 | 04-17 | 数式設計 | 数式構築時に sheet.getLastColumn() で最終列を取得したが、MAS-020 の YoY 差異列(O-Z)まで含まれて列範囲が想定外に膨張 | MAS-003 Step 2 | getLastColumn() は「値または書式が入っている最右列」を返す仕様。YoY 差異列が空でも書式があれば返される | 月列は C-N の 12 列固定にハードコード。動的取得する場合は「値の有無」まで検証(空セルは除外) |
| 22 | 04-17 | 数式設計 | String.trim() で先頭のラベル装飾を除去したつもりが、全角スペース U+3000 が残って MATCH が失敗 | MAS-003 Step 2 | JavaScript の trim() は ASCII 空白(\s)のみ対象で、全角スペース U+3000 を除去しない | ラベル正規化では .replace(/[\s\u3000]+/g, '') で全角スペース明示除去。または正規化関数を Utils に集約 |
| 23 | 04-17 | 数式設計 | sheet.getRange('B2').setValue("2026-03") で YYYY-MM 形式の年月を書き込んだら、Sheets が「2026 引く 3 = 2023」の負数として自動パース | MAS-003 Step 2 | Sheets は文字列を見て自動的に数式/日付/数値として解釈する。マイナス記号を減算演算子と誤認 | 文字列強制は apostrophe 前置で setValue("'2026-03")、または setNumberFormat('@') でセル書式をテキストに固定してから書き込む |
| 24 | 04-17 | 数式設計 | MATCH("✨ 売上総利益", ARRAYFORMULA(SUBSTITUTE(...))) でラベル検索する数式を組んだが、絵文字・改行・全角スペースの組合せで MATCH が壊れる | MAS-003 Step 2 | 数式側でのラベル正規化は脆弱: ARRAYFORMULA が大規模範囲に展開される、SUBSTITUTE で除去する文字を事前列挙しなければならない、Sheets の Unicode 正規化挙動が不透明 | 数式側でラベル検索をせず、GAS 側で行番号を事前特定してリテラル埋め込み (INDEX('61_pl_monthly'!$C:$N, 9, 8))。findRowInColA_() 等のヘルパーで A 列スキャン + 正規化を JS 側で実施 |
共通の根本原因と対策
#21-#24 は全て MAS-003 Step 2 の KPI ダッシュボード数式実装時に、「Sheets の数式機能でラベル解決しようとした」 設計方針の脆弱性が連鎖的に顕在化したもの。
対策(MAS-003 Step 2 で実装に反映済み):
- ラベル解決は GAS 側で完結: A 列スキャンで行番号を取得 →
INDEX('sheet'!$C:$N, row, col)形式のリテラル埋め込み数式を生成 - 動的列範囲取得の禁止: 月列は C-N の 12 列固定。YoY 差異列 O-Z との混同を避ける
- 文字列セル書き込み: 日付・年月風の文字列は apostrophe 前置 or
setNumberFormat('@') - 文字列正規化の Unicode 対応:
.replace(/[\s\u3000]+/g, '')パターンを徹底 - ARRAYFORMULA + MATCH + SUBSTITUTE の組合せは原則禁止: GAS 側で計算してから埋め込む方式に統一
仕様書記述で防げたもの(#18-#20)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 18 | 04-17 | 仕様書記述 | 仕様書が SHEET_DEFAULTS へ3要素配列を追加するよう指示したが、実際の型は { pattern, prefix, defaults } オブジェクト配列でシートキー登録用ではなかった | MAS-003 Step 1 | 名前「SHEET_DEFAULTS」から用途を推測し、Grep の部分ヒットだけで判断した。Read で構造を確認しなかった | 仕様書で既存データ構造に言及する際は必ず Read で型と要素形を確認してから記述 |
| 19 | 04-17 | 仕様書記述 | 仕様書が「03_sys_params にシートキーが登録される」と記述したが、実際の登録先は 01_sys_config(Constants.CONFIG_SHEET) | MAS-003 Step 1 | シート名を記憶で書いた。Utils.getSheetNameByKey と confSheet.appendRow の参照先を Read しなかった | シート名・定数名は Read で Constants.CONFIG_SHEET 等を参照先ごと追跡してから記述 |
| 20 | 04-17 | 仕様書記述 | 仕様書が動作確認で「🔧 システム初期化 → MST_CONF_SHEETS 初期化」と記述したが、そのメニューは存在しない(実在は「🔧 開発・設定 → 全シートのスキーマとUIを最新化(DDL)」) | MAS-003 Step 1 | メニュー名を造語した。onOpen() の ui.createMenu を Read しなかった | 動作確認手順に固有名詞(メニュー・関数・シート名)を書く前に、該当コードを Read して実在する文字列だけ引用 |
共通の根本原因と対策
#18-#20 は全て同日の MAS-003 仕様書作成時に、1 回の「コード未読 → 名前から類推」で 3 件まとめて埋め込まれた。
対策(汎用プロンプトの Phase 1-D に反映済み):
- Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず
Readで行う - 仕様書に書く以下の要素は、全て Read で裏取り済みのものに限定:
- データ構造の型・フィールド名
- 変数名・関数名・シート名・メニュー名・定数名
- 行番号・呼び出し経路
- 「この定数は〜用だろう」「このシートは〜だろう」と推測した瞬間に手を止めて Read
並列実装の対称性漏れ(#25)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 25 | 04-20 | 並列実装 | 銀行・クレカの類似2処理(501_cc_importer.js / 502_bank_importer.js)の片側だけに設定が漏れ、CC UNMATCHED 時の 確認FLG=FALSE セットが明示されていない状態だった。銀行側は明示的に FALSE セットしていたが、CC 側は暗黙のデフォルト値(空欄)に依存していた | MAS-159 実装時(PR #221)に検出、同 PR で drive-by fix | bank / cc のような並列実装を片方だけ見て編集し、もう一方の対称性チェックを怠った。書き込み系の分岐が「明示」と「暗黙デフォルト」で非対称になっていることを発見できなかった | 並列実装の編集時は必ず両方のコードを並べて対称性チェック。書き込み系は全分岐で明示的に値を set する(暗黙の初期値・空欄に頼らない)。Grep で setValue / appendRow 等の書込みポイントを bank / cc 両側で洗い出し、同じ列に対する書込みパターンが対称か確認 |
GAS マニフェスト・外部 API 仕様変動(#26-#30)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 26 | 04-20 | GAS マニフェスト設定の見落とし | mas/appsscript.json の oauthScopes を部分宣言したことで、GAS のスコープ自動検出が完全オフになり、既存機能(SpreadsheetApp / DriveApp / MailApp / Utils.auditLog 等)が一斉に "Specified permissions are not sufficient" で動作不能に | MAS-206 実装時(PR #245)に検出、fix コミット 023e773 で oauthScopes を削除して復旧 | GAS の oauthScopes は「明示宣言 = 自動検出完全オフ」という排他仕様。部分宣言すると自動検出されていた spreadsheets / drive / mail / ui 等が全て消失する。Admin SDK 等の Advanced Service 用スコープを「1 個足す」つもりで oauthScopes フィールド自体を追加してしまったのが原因 | Advanced Service で済むスコープは enabledAdvancedServices 側に宣言(GAS が自動付与)。どうしても oauthScopes に手動宣言が必要な場合は、既存コードで使用中の全スコープを GAS エディタ「プロジェクト設定 → OAuth スコープ」から抽出し、完全列挙した上で新規スコープを追加する。dev で主要機能(サイドバー / バックアップ / RPA)の一通り動作確認してから prod push |
| 27 | 04-20 | 外部 API 仕様変動への過度な依存 | AdminReports.Activities.list(..., { eventName: 'acl_change' }) でエラー "Event acl_change not found in manifest"。acl_change は旧仕様の名称で、現行では change_user_access / change_document_access_scope 等の粒度別名に分化していた | MAS-206 実装時(PR #245)に検出、fix コミット 439dcde で eventName フィルタ削除し post-processing 方式に変更 | Google Admin SDK 等、Google 社が更新する API のイベント名・フィールド名は時期で変わる。古いブログやチュートリアルに載っている名前がそのまま通用する保証はなく、eventName のようなサーバー側フィルタに依存すると API 仕様変更のたびに壊れる | 現行の公式ドキュメントで必ず確認(2025 年以前の記事は信用しない)。可能な場合は名前指定を避け、API に広めに問い合わせて post-processing(自前のフィルタリング)で絞る方式が堅牢。例: eventName 無指定で全 Drive アクティビティを取得 → target_user / target_domain パラメータを持つイベントのみ抽出。エラーメッセージ "not found in manifest" 等は名前変更サインとして認識。仕様書に API 名を明記する際は**「YYYY-MM 時点」のタイムスタンプを併記** |
| 28 | 04-27 | GAS 末尾 _ 関数の private 扱い | MAS-232 Stage 2 で SPA サイドバーが「google.script.run.getInitialStateForSpa_ is not a function」で起動不能。GAS 仕様で関数名末尾の _ は private 扱いとなり、google.script.run / doGet / doPost 等のクライアント呼び出しから到達できない。同名で末尾 _ なし関数も存在しないため undefined 扱いとなった | MAS-232 Stage 2 dev 投入時に検出(main 側 PR #372 / commit da1e549 で getInitialStateForSpa_ → getInitialStateForSpa にリネームして復旧)。仕様書 dev_mas-232_sidebar_spa.md v1.1 で UI 層分離設計時に末尾 _ を付けて記述したまま実装に流れた | GAS の命名規約(末尾 _ = private)と SPA 設計の「google.script.run 経由でクライアントから呼び出される public エンドポイント」が衝突。private helper(_setSpaView_ / _consumeSpaView_ 等)と public エンドポイント(getInitialStateForSpa 等)の命名規約を spec 起草時に区別できていなかった | クライアント呼び出しを伴う GAS 関数は末尾 _ を付けないを規約化。具体的には: ①google.script.run.foo() から呼ばれる関数 / ②doGet(e) / doPost(e) / ③onOpen(e) / onEdit(e) 等のトリガー / ④MENU_DEFINITION.funcName で参照される関数 — これらは全て末尾 _ 禁止。逆に完全な private helper(同一ファイル内からのみ呼ばれる内部関数)には末尾 _ を付ける(_setSpaView_ / _consumeSpaView_ / _calcInternal_ 等)。仕様書起草時は関数を「クライアント到達 / GAS 内部のみ」の 2 区分で明示し、命名を分ける。追加チェック: spec 内の関数名 grep で末尾 _ 関数のうち google.script.run 文脈で参照されているものがないかを Read で確認 |
| 29 | 04-27 | google.script.run V8→Java シリアライズの silent null | MAS-057 Phase 3 SPA で getInitialBootstrapData() 等が withSuccessHandler に null を返す。withFailureHandler は呼ばれず・throw もなく・GAS エディタ直接実行では JSON.stringify も成功するため検知困難。HAR で op.exec[0, null, "<Java HashMap.toString>"] — 第二要素が null・第三要素に Java toString が入る | MAS-057 Phase 3 実装時 (PR #379) に実機検出。原因は Constants.INCOME_TAX_BRACKETS.brackets[6].upTo = Infinity を bootstrap レスポンスに含めていたこと | Infinity / -Infinity / NaN は V8→Java ブリッジでシリアライズ失敗するが、エラーが UI まで伝播しない (Java 側で例外が握り潰される)。GAS エディタから直接実行すると JSON.stringify は成功する (V8 内では問題ない) ため、再現性が「Web App 経由のみ」に限定され検知が極めて困難 | 対策 (3 段階): ①bootstrap で既知の Infinity を Number.MAX_SAFE_INTEGER (9007199254740991) に scrub する防衛コードを書く / ②Repository 経由で取得する数値も Number.isFinite() で防御的にチェック / ③受信側 (クライアント) で state === null の場合に「サーバー側で Infinity / NaN / 関数 / 循環参照を含めていないか」のエラー表示。切分テクニック: (a) 静的 probe 関数 (Date と string のみ返す) で通信経路を確認 / (b) 関数内で JSON.stringify(result).length を console.log / (c) それでも null なら必ず Infinity / NaN / 関数 / 循環参照を疑う / (d) 最終手段は HAR エクスポートで op.exec[0, ...] の中身を確認 |
| 30 | 04-27 | Vertex AI preview モデルは個別有効化必須 | MAS-057 Phase 3 で gemini-3-pro-preview / gemini-3.1-pro-preview を呼ぶと bizlp-gas-accounting-dev の Vertex AI で HTTP 404 (Publisher Model not found) | MAS-057 Phase 3 実装時 (PR #379) に実機検出。gemini-2.5-pro は動作するが、3.x 系 preview モデルは事前有効化が必要だった | Vertex AI の Generative AI Studio / Model Garden で publisher model を個別に Enable する必要がある + 48 時間の課金履歴経過が条件になっているケースあり (preview モデルは GA より厳格)。仕様書では「Vertex Gemini 経由で呼べる」と一括で書いてしまったが、preview と GA で制約が異なる | モデル切替を 03_sys_params で動的化 (F57_INSIGHT_AI_MODEL + F57_GEMINI_MODEL_OVERRIDE_PRO/FLASH) し、preview モデル試験時は CUSTOM オプションで切替可能な設計とする (UI 上 model_id 直接入力)。default は GA リリース済モデル (gemini-2.5-pro 等) を採用。preview モデル使用時は仕様書に「Model Garden で個別 Enable + 48h 経過必要」を明記する。仕様書記載の API 名は 「YYYY-MM 時点」のタイムスタンプを併記 (#27 と同じ規則を踏襲) |
並列実装・並行開発の同期失敗(#25, #31)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 31 | 04-27 | 案件 ID 採番衝突 (TODO_future.md 同期遅延) | MAS-057 Phase 3 実装後の振り返りで派生した「配当ミックス最適化」案件を main 側プロンプトが「MAS-060 として起票」と指定したが、MAS-060 は既に「組織構成シミュレーター」(2026-04-25 起票) として確定使用済 → 番号衝突。sub 側で MAS-066 に振り直して起票 (PR #382) | 2026-04-27 main 側が MAS-057 Phase 3 PR #379 を実装する間に sub 側で MAS-060〜MAS-065 が起票されていたが、main 側が git pull 前に MAS-060 を新規予約してしまった。sub 側が git pull origin main 後の TODO_future.md grep で衝突を検出して振り直し | 複数ワークスペース (main / sub) での並行開発時の TODO_future.md 同期遅延。main 側が origin/main を fetch せずに新規案件 ID を予約すると、sub 側で確定済の番号と衝突する。CLAUDE.md の「複数ワークスペースでの並行開発ルール」§ファイル担当マトリクスでは TODO_future.md は sub 専属だが、新規案件 ID 予約は両側で発生しうる (起票プロンプト発行は main 側が行うことも多い) | 新規案件起票プロンプト発行前に必ず以下を実施: ①git fetch origin main && git diff HEAD origin/main -- docs/_internal/TODO_future.md で main の最新 ID 予約状況を確認 / ②`grep -nE '| F-[0-9]+ \ |
React / SPA 実装の落とし穴(#32-#33)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 32 | 04-28 | React TDZ エラー (useMemo factory + const arrow ヘルパー後置) | MAS-058 Step 5 (PR #400) RequiredRevenuePanel.tsx 実装中、初回 render で Cannot access 'isError' before initialization エラー発生。useMemo factory 内で参照する isError ヘルパーを const arrow function で後ろに宣言していたため Temporal Dead Zone (TDZ) 違反 | MAS-058 Step 5 SPA 実装中 (PR #400) に検出 | JavaScript の const / let 宣言は TDZ (宣言行に到達するまで参照不可) を持つ。function 宣言は hoisting されるが、const arrow は hoisting されない。React の useMemo(() => helper(...), [...]) の factory は render フェーズ中に同期実行されるため、factory 内で const 宣言したヘルパーへの参照が「宣言より前」になると TDZ エラー | 対策: ①ヘルパー関数は useMemo 呼出より前に宣言する (推奨)、②function 宣言を使う (hoisted・順序不問)、③ヘルパーを useCallback でラップして依存配列管理する場合も宣言順を意識。追加チェック: コンポーネント内の const/let 宣言ヘルパーが、その前で参照されていないかを ESLint no-use-before-define ルールで検出可能 (TypeScript strict 設定推奨) |
| 33 | 04-28 | 新規 GAS ドメインエンジン追加時の SPA 副作用 import 漏れ | MAS-066 PR (#402) で mas/400_domain/449_dividend_mix_optimizer.js を新設し webapp_client/scripts/sync-engines.mjs の同期対象に追加したが、webapp_client/src/cockpit-main.tsx への副作用 import を忘れていたため、cockpit 上で配当を変更しても計算結果に反映されないバグが発生 | MAS-066 実装中 (PR #402) に検出。fix(MAS-066) commit 41305ea で cockpit-main.tsx に import './engines/449_dividend_mix_optimizer.js' を追加して修正 | window 露出方式 (注意事項 #14 (b) 採用) では mas/400_domain/{xxx}_*.js を sync-engines.mjs でコピー → webapp_client/src/engines/ に配置 → ビルド時に if (typeof window !== 'undefined') window.XxxEngine = XxxEngine; が末尾に自動付与される。ただし cockpit-main.tsx で副作用 import (import './engines/xxx_*.js') しないと bundle に含まれず window.XxxEngine が undefined になる。Vite の tree-shaking が「副作用なし」と判断して除外する典型パターン。sync-engines.mjs 自体はファイルコピーのみで bundle 登録までは面倒見ない | 新規ドメインエンジン追加時の必須チェックリスト: ①mas/400_domain/{xxx}_*.js を新設・IIFE + var XxxEngine = (function(){ ... })() パターンで実装 / ②webapp_client/scripts/sync-engines.mjs の ENGINES_TO_SYNC 配列に追加 / ③**webapp_client/src/cockpit-main.tsx (or sidebar-main.tsx 等のエントリポイント) に副作用 import を追加** ← 必須・抜け漏れ多発ポイント / ④npm run build 実行 / ⑤cockpit/sidebar の動作確認時に console.log(window.XxxEngine) で undefined でないことを確認 / ⑥単体テスト (F66-01 等) は GAS 側のみで PASS しても SPA 側で動かないケースがあるためブラウザでの実機確認まで含めて完了判定。sync-engines.mjs の出力ログに「次の手順: cockpit-main.tsx に副作用 import を追加してください」を強調表示する改修を将来検討 |
計画 B/S と実績 B/S の構造的乖離(#34)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 34 | 05-01 | 計画 B/S が実績 B/S と大きく乖離 (cash plug 過小) | PR #460 で 73_bs_plan の現預金が実績 71_bs と乖離 (2026-02 で約 -605K)。計画 B/S で資金見通しが過度に悲観的になり、経営判断ミスリードリスク。兆候: (a) 計画側現預金が実績より大幅マイナス・(b) 未払系負債が異常に少ない・(c) 月次で乖離幅が拡大 | MAS-300 (BUG_tracking) として記録。PR #460 で3 段階のロジック改善 (d748ed5 → 3aaa04e → c21c3dc → 1f04ef5) を経て案 A 改 2 に確定 | 「計画 = 全 INV 即決済前提」の単純化により、計画側で未払系負債が積み上がらず現預金がマイナス側に振れる構造的バグ。実績側 (PHASE 2) では 決済日_実績 ベースで期ずれが正しく解消されるが、計画側 (PHASE 1) では同じ仕組みが欠落。月次で乖離幅が拡大するのは「未消込 INV の累積」が cash plug として効かないため (実績では未払金として B/S に積まれるが、計画では即時決済扱いで負債側に出てこない) | mas/600_report/601_datamart_ingest.js の dmIngestPlanData_ で INV の 決済日_実績 列を期ずれ解消月の判定起点とする: (a) 決済日_実績 記入済 → 実績 PHASE 2 と同月で期ずれ解消 (sStr = settleActStr) / (b) 決済日_実績 空 + 決済日_計画 ≤ 当月 → 未消込のまま残す (sStr = '9999-12') / (c) それ以外 → 決済日_計画 をそのまま使用。残乖離 ±30K は地代家賃の前払 INV 表示差 (71 では「未払費用 -30K」・73 計画では「前払費用 +30K」となるが cash plug 効果は同値で許容)。追加チェック: 計画 B/S と実績 B/S の差分は当月決算で ±50K 以内 (地代家賃前払等の表示差を除く) を許容範囲とし、超過時は警告表示 (MAS-085 整合性チェックパターンを応用)。設計原則: 計画 B/S 生成時は実績側の決済日 (決済日_実績) を SSoT として優先し、計画日 (決済日_計画) は補助情報として扱う |
GAS API: フィルター適用中の連続 setValue が silent-fail(#35)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 35 | 05-01 | フィルター適用中の連続 setValue で後続列が silent-fail | PR #465 で applyBankSettlement のフィルター silent-fail 修正。33_wrk_bank に「決済ステータス = 未処理」フィルターを設定した状態で消込実行すると、消込後に 33タブを見ても 決済日_実績 列が空のまま。Action B (自動仕訳生成) が起動条件 (33タブの「消込済 かつ 自動仕訳JNL_ID あり」判定) を満たさず止まる二次被害あり。兆候: スクリプト実行は成功 (例外なし・ログも正常) するが、特定列だけ値が入らない (「消込済」は書かれるが他は空)。フィルター解除後に再実行すると正常動作するため再現条件が掴みにくい | MAS-302 (BUG_tracking) として記録。PR #465 commit c069b63 で修正 | 1 つ目の setValue でフィルター対象列 (例: 「決済ステータス」列を「未処理」→「消込済」に変更) が変わると、Google Sheets が即フィルター再評価を実行して対象行が hide される。hide 後の行に対する後続の setValue は API としては成功扱いだが実際には書き込まれない silent-fail が発生する。Sheets API の Range が hide 状態の行にバインドされた直後の操作で発生するレースコンディション | 同一行への複数セル更新は range.setValues([rowVals]) で 1 つの API call にまとめて原子化する。例: range1.setValue(A); range2.setValue(B); range3.setValue(C); range4.setValue(D) (4 call・各 call の間にフィルター再評価が挟まる) → sheet.getRange(row, col, 1, 4).setValues([[A, B, C, D]]) (1 call・原子的書込・フィルター再評価が挟まる隙なし)。派生注意: setBackground 等の書式系は filter 状態に左右されないため分離可能。setValues 化が困難な場合は、フィルター対象列を最後に更新するワークアラウンドも可だが、フィルター列追加時の保守コストが上がるため非推奨。チェック: フィルター適用シート (flagTabs 対象) への複数 setValue 呼出は code review で setValues 化を必須とする (33_wrk_bank / 23_bud_subscription 等) |
単一マッチ前提のロジックが拡張機能 (合算 / 部分一致) に未追従(#36)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 36 | 05-01 | 単一ケース前提のロジックが多重ケース拡張時に silently 機能しない | PR #465 の Pass 2.5 ソフト合算追加後、applyBankSettlement の差額記録ロジックが stlIds.length === 1 の単一マッチ条件のみ対応しており、Pass 2.5 (合算 ±少額差) ヒット時の差額が会計上ロスト。71_bs 2026-04 現預金乖離 98,837 円のうち 1,000 円分が本パターン起因 (武生年金: 銀行 149,950 vs STL 合計 150,950)。兆候: (a) 既存単一機能テストは PASS する (b) 拡張機能 (合算等) で「動いているように見えるが副次データ (差額・摘要・科目) が抜ける」 (c) 数ヶ月後に B/S 集計時の cash plug 不整合で発覚 | MAS-303 (BUG_tracking) として記録。MAS-338 (dev_mas-338_settlement_diff_handling.md) として spec 化済 | 拡張機能 (Pass 2.5 ソフト合算) を追加した PR #465 では「合算マッチ機能 + 銀行明細との金額判定」までを実装スコープとし、消込後の副次処理 (差額記録 / 科目推定 / 監査ログ) の合算対応は別 PR スコープに切り分けたため、「単一マッチ前提のロジック (if (stlIds.length === 1))」がそのまま生き残り、新機能の差額が落ちる片肺状態に。「単一前提ロジック」の存在を PR #465 のレビュー時に検出できなかったのは、合算機能のテストが「合算マッチが成功すること」までしかカバーしていなかったため | (1) 拡張機能追加時のチェックリスト: 既存ロジックの「if (count === 1) / [0] / 単一前提の if 分岐」を grep で全件抽出し、拡張機能でも対応必要かを review checklist に明文化。(2) findComboGroup_ 等の拡張ヘルパー追加時は、その下流で「合算結果を消費する全箇所」を call graph で追跡し、stlIds.length を見ている箇所を網羅 (差額記録 / 監査ログ / UI 表示)。(3) テストカバレッジ: 単一マッチ + 合算マッチ + ソフト合算の 3 ケース全てで「副次データ (差額・科目・ログ) が正しく書かれているか」を検証する E2E テストを必須化 (F338-01 〜)。(4) 設計原則: 拡張機能 (合算 / 部分一致 / 差額あり) は「既存機能の置換」ではなく「上位 superset」として設計し、stlIds.length === 1 の単一前提分岐は速やかに >= 1 化する (差額按分ヘルパー chooseDiffTargetStl_ を介して同一インターフェースで処理)。チェック対象 spec: MAS-338 (本パターンの解消・実装着手判断待ち) |
Walking Skeleton / ADR-0028/0029 準拠漏れ(#37-#39)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 37 | 05-13 | 監査ログ書込の silent-fail 検知不能 | UC スライス実装後、Utils.auditLog の呼び出しはあるが 98_audit_log シートに行が追記されない。スクリプト実行は例外なし・ログも正常に見えるため数スプリント後まで気づかない。兆候: (a) Utils.auditLog 呼び出しコードは存在する (b) 98_audit_log シートの行数が実行回数と一致しない (c) 監査要件の証跡が空 | ADR-0028/0029 Walking Skeleton 必須要素③の欠落 | Walking Skeleton の「③監査ログ (Log)」をテストせずに skeleton ステータスへ移行したため、呼び出し引数の型不一致 (例: undefined を渡す) が silent-fail として見逃される | Walking Skeleton の test_{slice_id}_audit_logged_() テストを必須化: 98_audit_log シートの行数を実行前後で比較し、PASS エントリが追記されることを assert する。skeleton ステータス移行条件に本テストの PASS を含める。Utils.auditLog は第 1 引数 sliceId(文字列)・第 2 引数 status('PASS' または 'FAIL')・第 3 引数 detail(任意文字列)の型チェックを関数先頭で行い、型不一致時は console.error ではなく throw させる |
| 38 | 05-13 | Feature Flag キーが PropertiesService 9KB / 30 文字制限を超過 | UC スライスで Feature Flag キーを FF_UC{N}_S{NN}_{verb} 形式で命名したが、キー文字列が 30 文字を超過し PropertiesService.setProperty が例外をスローする。兆候: (a) Feature Flag の設定時にのみ例外が出る (b) キー名が FF_UC10_S12_enable_all_the_things 等の 30 文字超 | ADR-0029 C1 で Feature Flag キー命名規則を定めたが、FF_UC{N}_S{NN}_{verb} の最大長チェックを仕様書に明記していなかった | FF_ (3) + UC (2) + N (1-2) + _ (1) + S (1) + NN (2) + _ (1) + verb の最大長は 30 - 10 = 20 文字。動詞が長い場合 (例: enable_monthly_closing_report) は 30 文字を超過する | 命名規則を FF_UC{N}_S{NN}_{verb} かつ 総長 30 文字以内 に厳格化: verb は最大 18 文字 (N=1桁・NN=2桁時) 〜 最小 16 文字 (N=2桁時)。仕様書作成時に scripts/B4_inject_walking_skeleton.js がキー長チェックを自動実行しキー超過を警告する。test_{slice_id}_error_handling_() テスト内で実際にキーを PropertiesService にセット・ゲットして例外が出ないことを確認する |
| 39 | 05-13 | 6 分制限を貫通する UC スライス粒度 | UC スライスの実装後、実データで実行すると 6 分制限(360 秒)を超過してタイムアウトが発生する。兆候: (a) 少量データのテストは PASS する (b) 本番スプシで実行すると「最大実行時間を超えました」エラー (c) _RUNTIME_METRICS シートに実行時間の記録がない(タイムアウトで measureRuntime_ の finally ブロックが未到達) | Walking Skeleton の仕様書レビュー時に「6 分制限チェック(推定実行時間)」の記載が不十分だったため、スライス粒度が大きすぎることを実装前に検知できなかった | UC スライスが「1 UC スライス = 1 つのビジネスシナリオ全体」になっており、行数が多いシートへの全行処理が含まれている。仕様書の「推定実行時間: _____ 秒」が空欄のまま CONDITIONAL GO 判定を通過した | 仕様書の Walking Skeleton チェックリストの「推定実行時間」空欄は GO 判定不可: scripts/B5_review_specs_by_gemini.js のレビュープロンプト §G-3 で 6 分見積もり未記載を Critical 指摘対象とする。6 分超過リスクがある場合は chunk 分割(Properties 進捗保存 + Time-driven Trigger)の実装方針を仕様書に明記すること。test_{slice_id}_input_() テストで Utils.measureRuntime_ の計測値が 60 秒未満であることを Walking Skeleton 完了条件に含める(余裕率 6 倍: 少量テストデータ × 6 ≦ 360 秒) |
ドキュメント大規模改訂時の構造的落とし穴(#40-#42)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 40 | 05-20 | ドキュメント | index.md / SUMMARY.md の中身 swap (PR #874) 後、既存リンク [index.md §1.9](../index.md) が意味的に壊れた状態で merge され、PR #873 (Glossary SSoT) で発覚。Step 4 audit (PR #876) でも追加で dev_mas-090 L336 の [5.5 預り金・源泉税](../index.md) 等が swap の影響を受けている可能性が判明 (legacy-dev 故触らず保留) | PR #874 / PR #876 / PR #873 | ファイル間の content swap は anchor 参照を破壊するが、swap 実施 PR で「grep -rnE 'filename\.md(#[a-zA-Z0-9-]+)?'」での参照全件抽出を行わなかった。build 成功 ≠ anchor 解決成功 (anchor は missing でも build は通る) | content swap / 大規模 section 移動 PR の必須チェック: ①swap 対象ファイル名を grep -rnE 'filename\.md(#[^)\s]*)?' docs/ で全 link を抽出 → ②各 link が swap 後も解決可能か手動 or スクリプトで検証 → ③Cloudflare Pages preview で実際の navigation を merge 前に確認。docs-build.mjs の build 成功は anchor 解決を保証しないため、別途 link-check が必要 (将来 CI 化を検討) |
| 41 | 05-20 | ドキュメント | adr-lint_rules.md が Phase 2b で 637 行に到達し Anthropic Skill 500 行制約超過 → Phase 2c で案 X5 (rules/<id>.md 個別ファイル分離) へ緊急移行 (PR #866) | ADR-0054 / PR #865 / PR #866 | Progressive Disclosure 設計時に「閾値到達後の分割パス」を ADR §6 撤退条件で定義済だったが、Phase 2b で残 12 rule Detail を一度に追加して閾値超過。Phase 単位で 400 行 (WARN) 段階での移行判断を行わなかった | ADR §6 撤退条件で 400 行 (WARN) 段階での移行判断を必須化。CI で 400/500 行二段階閾値を強制 (PR #867 で scripts/adr-lint-doc-consistency.mjs 実装済、main doc 134 lines (limit 400 WARN / 500 FAIL) 出力で監視)。Phase 2 系の大量追加 PR では事前に行数を見積もり、400 行超過見込みなら Phase 分割直前に X5 (個別ファイル分離) を先行実施してから本体追加 |
| 42 | 05-20 | Git 運用 | pre_bash_guard.sh が git push --force-with-lease を自動実行から block するため、PR rebase 後の push が自動 PR フローで詰まる (PR #873 で発生)。兆候: rebase は成功・build/lint も pass しているのに自動 push が「permission denied / hook blocked」相当で失敗 | scripts/hooks/pre_bash_guard.sh / CLAUDE.md "Prohibited" | 不可逆操作 (force push) を技術的にブロックする設計は意図通り正しい (.claude/settings.json の permissions.deny + PreToolUse Hook)。ただし PR rebase 後の正当な force-with-lease push もブロックされるため、自動フローでは詰まる | PR rebase + force-with-lease push のオペレーション手順を明文化: ①ローカルで git rebase main → ②検証 (docs-build.mjs / docs-nav-lint.mjs / adr-lint-doc-consistency.mjs) → ③! git push --force-with-lease origin <branch> をユーザに依頼 (! prefix で hook を経由しない sandbox 外実行) → ④gh pr view N --json mergeable,headRefOid で remote 反映を確認。docs/_internal/05_how-to/git_workflow.md の rebase 章に本パターンを記載 (将来) |
Deploy: 無効な manifest 値 → clasp 3.x silent skip → 古い bundle が動き続ける(#43)★ 最重大級
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 43 | 05-22 | Deploy 運用 | OCR Bench で mas/appsscript.json の webapp.access を ANYONE_WITH_GOOGLE_ACCOUNT に設定。clasp 3.x はこの値を Invalid manifest と判定し clasp push 全体を silent skip → deploy.sh だけが走り「✅ Deploy complete」と表示されるが、実際は古い bundle が GAS に残ったまま約 1 日間動作。ローカルで bbox padding / 印字手書き判定 / 罫線除去 / dilation / Claude validator / Self-Consistency / GT 全項目 / pdfjs scale 4.0 / pdf-lib CropBox を実装・「デプロイ完了」しても、ユーザのブラウザではすべて ddd3fa5 時点のコードが動く。「pdfjs blank」「PNG 出力」「click 不可」等の症状を 1 日かけて多方向から「修正」したが、すべて wrong target — 真の原因は push 自体が一度も走っていない だったため改善せず | OCR Bench Sprint (PR #891 → #892 → #901) | (1) ANYONE_WITH_GOOGLE_ACCOUNT は 存在しない値 (有効値: UNKNOWN_ACCESS / DOMAIN / ANYONE / ANYONE_ANONYMOUS / MYSELF)。Anthropic/Google ドキュメントの口語表現「anyone with Google account」を勝手に enum と同一視した思い込み / (2) clasp 3.x は manifest validation でこの値を reject するが、エラー出力は npm run deploy:dev 2>&1 | tail -3 で末尾 3 行に切り詰めると見えない / (3) deploy:dev = push:dev && deploy.sh の && は push:dev の最後の cmd (clasp push) が exit 0 で帰れば通過。clasp は "Skipping push." と表示しつつ exit 0 で抜けるため、deploy.sh が無批判に走り「✅ Deploy complete」を出す / (4) deploy.sh の clasp deploy --deploymentId X は GAS 側に既にある最新ファイル群で新 deployment 版を作るだけで、ローカルファイルと GAS ファイルの一致は保証しない | A. 検証 (恒久対策): ①scripts/pre-push-check.sh 末尾で mas/appsscript.json の access 値を有効値リスト (UNKNOWN_ACCESS|DOMAIN|ANYONE|ANYONE_ANONYMOUS|MYSELF) と grep 比較し外れたら exit 1 / ②deploy:dev の push:dev 末尾を clasp push --force に変更 (change detection で skip するのを防止) ※本 PR で実施済 / ③deploy.sh の冒頭で deployed bundle の sentinel string を fetch して検証: mas/templates/ocr_bench_shell.html に build 時 unique timestamp や git SHA を埋め込み (例: <meta name="build-sha" content="aacfa5d">)、deploy 直後に curl /exec?... でその string を取り、ローカルファイルの該当箇所と一致しなければ exit 1。B. 観察 (短期対策): ①deploy 実行時は tail -3 ではなく tail -30 以上で Skipping push. / errors / warning を必ず確認 / ②ユーザから「修正が反映されていない」報告を受けたら コード変更でなく deploy 状況を最初に疑う (HAR ダウンロードして bundle 内に新シンボル grep 等)。C. 設計思想: mas/appsscript.json のような外部 schema に依存する設定値は enum を勝手に書かず、必ず公式リファレンスを 1 次資料で確認。ChatGPT 等他 AI から提示された値も鵜呑みにしない |
Deploy: worker src PR の merge = 即本番デプロイ → in-flight 審査 run を kill(#44)
| # | 日付 | カテゴリ | 失敗内容 | 関連案件 | 根本原因 | 対策 |
|---|---|---|---|---|---|---|
| 44 | 06-04 | Deploy 運用 | PR #1431 (Gate 1 プロンプト改善・src/nodes/socratic.ts 含む) を merge → deploy-worker.yml が 12:05-06 (UTC) に自動デプロイ → その時刻に実行中だった審査 run (drp-layered-docs-architecture 再投入) の queue consumer invocation が kill され、再配信も isInflightRedelivery ガード (running + updatedAt 20 分鮮度) で ack-skip → 永久無進捗 → EC-3 watchdog が idle 1200s で error 終端 (12:26、telemetry に watchdog-timeout 行記録)。審査 ~10 分ぶんの LLM 課金と gate 進捗が喪失 | PR #1431 / DRP-376 残課題① / deploy-worker.yml | (1) decision-pipeline は merge = 本番デプロイ の配線 (deploy-worker.yml が drp/src|public|wrangler.toml の main push で発火) だが、デプロイと実行中審査 run の排他機構がない — Cloudflare Workers のデプロイは in-flight の Queues consumer invocation を graceful drain しない / (2) merge 操作者 (Claude) からは実行中 run を一覧する手段がない (list-sessions エンドポイント無し・telemetry は run 終端時書込のため in-flight が見えない) / (3) フル run ~10-16 分の長尺と kill 後再開不能 (gate 単位 chunk 化未実装 = DRP-376 残課題①) の合せ技で被害が最大化 | A. 運用 (即日): worker src (drp/src|public|wrangler.toml) を触る PR の merge 前に「審査 run 実行中でないか」をユーザーに必ず確認 (CLAUDE.md Workflow 節に追記済)。確認できるまで merge 保留 / B. 検知: watchdog は設計どおり機能 (沈黙せず 20 分で error 終端 + telemetry 記録)。kill 直後の症状は「running のまま進捗停止」→ 20 分待てば watchdog-timeout で表面化 / C. 復旧: draft は KV に残存するため chat UI から再 run (新 worker 上で最初から) / D. 構造解消 (将来): queue-consumer-gate-chunking ADR (gate 単位 chunk 実行・kill 後再開可能) が審査 pass 済み・PR 起票待ち。採択されればデプロイ kill の被害は「実行中 chunk 1 個」に縮小 |