Claude CodeでSalesforce Flowをレビューする — flow-meta.xmlの点検手順
flow-meta.xmlを読む読み取り専用サブエージェントに、ループ内DML・faultパス欠落・ハードコードIDを点検させる手順と、スキャナーとの使い分けをまとめます。
Salesforce Flowは画面で組むものですが、リポジトリに入っているのは *.flow-meta.xml というXMLです。数百行のXMLを差分で眺めても、ループの中に更新要素があるかどうかは、人間にはなかなか見えません。Claude Codeでは、このXMLを読むだけの専用サブエージェントを .claude/agents/ に置き、ループ内のDML、faultパスの欠落、ハードコードされたIDの3点を点検させると、レビューの下ごしらえを任せられます。
この記事では、点検対象がXMLのどこに現れるかを押さえ、読み取り専用のサブエージェントを定義し、違反を仕込んだ例で点検の流れを追います。最後に、決定論的なスキャナーとの使い分けも扱います。XMLとサブエージェントの定義は説明用の例です。手元のFlowで動かして確かめてください。
点検する3つの違反は、XMLのどこに出るか
Flowの要素は、flow-meta.xml の直下に要素の種類ごとのタグとして並びます。点検を頼む前に、3つの違反がどのタグに対応するかを知っておくと、サブエージェントへの指示が短く済みます。
| 点検項目 | 見るタグ | 違反の形 |
|---|---|---|
| ループ内のDML | 見るタグloops と recordCreates / recordUpdates / recordDeletes | 違反の形ループの中から到達できる位置にレコード操作がある |
| faultパス欠落 | 見るタグrecordLookups、recordUpdates、recordCreates、recordDeletes、actionCalls、waits など | 違反の形要素内に faultConnector がない |
| ハードコードID | 見るタグstringValue などの値 | 違反の形特定の組織にしかないレコードIDが直書きされている |
Flowの静的解析ツールであるLightning Flow Scannerは、ループ内のDMLを recordDeletes、recordUpdates、recordCreates の3種で判定し、faultパスの欠落を recordLookups、recordDeletes、recordUpdates、recordCreates、waits、actionCalls、apexPluginCalls に対して見ています。サブエージェントに渡す観点も、この単位で書くと過不足が出にくくなります。
ループの終わりは noMoreValuesConnector で示されます。この接続の先に出てくるまでが「ループの中」です。XMLでは要素が上から順に並ぶだけで、実行の流れは targetReference の連鎖でしか分からないので、サブエージェントには接続をたどらせる必要があります。
読み取り専用のFlowレビュー用サブエージェントを定義する
サブエージェントは .claude/agents/ に置くMarkdownファイルで、必須のフロントマターは name と description だけです。tools を Read, Grep, Glob に絞れば、ファイルの書き換えやコマンド実行ができない点検専用になります。
---
name: flow-metadata-reviewer
description: Salesforce Flowのflow-meta.xmlをレビューし、ループ内DML・faultパス欠落・ハードコードIDを報告する。Flowの変更をコミットする前に使う。
tools: Read, Grep, Glob
model: inherit
---
あなたはSalesforce Flowのレビュアーです。渡された
*.flow-meta.xml を読み、次の3点を点検します。ファイルは変更しません。
1. ループ内のDML
- loops 要素ごとに nextValueConnector から targetReference を
たどり、noMoreValuesConnector の先に出るまでの要素を列挙する
- その中に recordCreates / recordUpdates / recordDeletes が
あれば違反として報告する
2. faultパスの欠落
- recordLookups / recordCreates / recordUpdates / recordDeletes /
actionCalls / waits に faultConnector が無ければ報告する
- triggerType が RecordBeforeSave のFlowの recordUpdates は除く
- waits のうち、待機の種類が WaitDuration と WaitDate のものは除く
3. ハードコードID
- stringValue に直書きされた15桁・18桁のIDらしい文字列を報告する
- テスト用の値や外部システムのキーの可能性があるものは
「判断に迷った箇所」に回す
- どの要素のどの項目かを、要素名(name)で示す
出力は「違反一覧」「判断に迷った箇所」の2見出しにします。
違反ごとに、ファイル名・要素名・要素の種類・理由を1行で書きます。description は、Claudeが「いつこのサブエージェントへ委任するか」を判断する文です。Flowの変更をコミットする前に、と使う場面を書いておくと、自動で委任される場面が増えます。確実に呼びたいときは @"flow-metadata-reviewer (agent)" force-app の差分を見て のように @ メンションで指定します。
.claude/agents/ に置いたファイルは、起動中のセッションでも数秒で検出されます。ただし、そのセッションで最初のエージェントファイルを作る場合など、再起動が要る例外が3つあります。新しい agents ディレクトリを作った直後、--add-dir で足したディレクトリ、--disable-slash-commands で起動したセッションです。
違反を仕込んだFlowで点検の流れを追う
点検の対象にする Update_Contacts.flow-meta.xml を、次のように用意します。レコードトリガーのFlowで、取引先責任者をループで回し、ループの中で更新しています。更新要素に faultConnector はなく、所有者のIDも直書きです。
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<loops>
<name>Loop_Contacts</name>
<collectionReference>Get_Contacts</collectionReference>
<nextValueConnector>
<targetReference>Update_Contact</targetReference>
</nextValueConnector>
<noMoreValuesConnector>
<targetReference>Finish</targetReference>
</noMoreValuesConnector>
</loops>
<recordUpdates>
<name>Update_Contact</name>
<connector>
<targetReference>Loop_Contacts</targetReference>
</connector>
<inputAssignments>
<field>OwnerId</field>
<value><stringValue>005000000000001AAA</stringValue></value>
</inputAssignments>
<object>Contact</object>
</recordUpdates>
</Flow>サブエージェントには、対象を明示して頼みます。
flow-metadata-reviewer で force-app/main/default/flows/
Update_Contacts.flow-meta.xml をレビューしてこの例なら、報告に期待するのは次のような形です。実際の文面はモデルや指示で変わるので、観点が揃っているかだけを見てください。
## 違反一覧
- Update_Contacts: Update_Contact (recordUpdates)
ループ Loop_Contacts の中から到達する。ループ内DML
- Update_Contacts: Update_Contact (recordUpdates)
faultConnector がない
- Update_Contacts: Update_Contact の OwnerId
005000000000001AAA がIDの直書きUpdate_Contact の接続は Loop_Contacts に戻っており、Loop_Contacts の nextValueConnector がこの更新を指しています。ループの中にあることは、接続を2つたどると分かります。
直し方は親の会話で頼む
サブエージェントは読み取り専用なので、書き換えは親の会話に戻ってから頼みます。ループ内DMLなら、ループでは Assignment 要素によってレコードコレクション変数へ追加し、ループの外で1回だけ更新する形です。IDの直書きは、Get Recordsで取った値か変数に置き換えます。faultパスは、エラーを扱う画面や通知の要素へ faultConnector をつなぎます。
例外を先に伝えておく
点検の精度は、例外をどこまで伝えたかで決まります。Lightning Flow Scannerのfaultパス判定のソースには、除外が2つあります。
- トリガー種別が
RecordBeforeSaveのFlowにあるrecordUpdatesは対象外にする waitsのうち、待機の種類がWaitDurationとWaitDateのものは対象外にする
前者は、保存前に起動するFlowが $Record を直接書き換える形で、別のDMLを発行しないためです。先ほどの定義に「RecordBeforeSave の recordUpdates は除く」「waits のうち WaitDuration と WaitDate は除く」と書いたのは、この2つの除外に合わせたものです。前者の一文がないと、保存前Flowの更新要素がすべて違反として並び、報告が読めなくなります。
ハードコードIDにも誤検出があります。15桁・18桁の英数字は、IDに見えて実際はテスト用の文字列や外部システムのキーかもしれません。報告の「判断に迷った箇所」に回すよう指示してあるので、見つかった件数をそのまま修正の件数と考えず、要素名を頼りに人が判定します。
スキャナーとサブエージェントを使い分ける
同じ3点は、決定論的なスキャナーでも調べられます。Lightning Flow Scannerはルール dml-in-loop、hardcoded-id、missing-fault-path を持ち、前の2つの重大度はerror、missing-fault-path はwarningです。Salesforce Code Analyzerも、対象にApexに加えてFlowなどのメタデータを挙げています。
Lightning Flow ScannerはSalesforce CLIのプラグインとしても使え、次のコマンドが載っています。
sf flow:scan # カレントディレクトリのFlowを走査
sf flow:scan --sarif > report.sarif # SARIF形式で出力特定のFlowのルールだけを外すときは、設定ファイルの exceptions に、Flow名・ルールID・結果名を書きます。
{
"exceptions": {
"MyFlow": {
"hardcoded-id": ["Old_Lookup_1"]
}
}
}使い分けの軸は、同じ入力に同じ出力が返るべきか、です。
| 場面 | 向くもの | 理由 |
|---|---|---|
| プルリクエストごとの合否判定 | 向くものスキャナー | 理由毎回同じ結果で、SARIFでCIに載せられる |
| 変更の意図を読んだ上での指摘 | 向くものサブエージェント | 理由接続をたどって「なぜここが問題か」を文章で返せる |
| 既知の例外の管理 | 向くものスキャナーの exceptions | 理由設定ファイルに残り、例外をFlow名とルールID単位で管理できる |
スキャナーで拾えた違反をサブエージェントが再確認する、という順序にすると、サブエージェントの出力を信用する根拠ができます。逆に、サブエージェントだけが見つけた違反は、スキャナー側のルールに足せないかを検討する材料になります。
点検の観点を足す — 同じXMLから読める5つの項目
3点に慣れたら、同じXMLから読める観点を足せます。Lightning Flow Scannerのルール一覧を手がかりにすると、足す候補がそのまま出てきます。
- 実行モード: ルート直下の
runInModeがSystemModeWithoutSharingだと、共有ルールを無視して全データにアクセスします。スキャナーはunsafe-running-contextとしてerrorにしています - 取得結果の空判定: Get Recordsはレコードが見つからないとnullを返します。直後に空判定の分岐がないFlowは、
missing-null-handlerの対象です - 秘密情報とURL: 認証情報の直書きは
hardcoded-secret(beta)、URLの直書きはhardcoded-urlが拾います。値はNamed Credentials、カスタム設定、カスタムメタデータへ寄せる方針です - 再帰: 保存後に起動するFlowが、起動元と同じレコードを更新すると再帰しうるため、保存前Flowに置き換える方針が
recursive-record-updateに書かれています - ループ内のアクション呼び出し: Apexアクションをループ内で何度も呼ぶ形は、コレクション変数を渡して外へ出す方針です
指示文には、観点を1項目ずつ「XMLのどのタグか」「違反の形」の2点で書きます。観点を増やしたぶん報告が長くなるので、出力の見出しを「違反一覧」の中で重大度別に分けるよう頼むと、読む順番が決まります。
チームの規約をCLAUDE.mdに置き、CIから呼ぶ
サブエージェントの指示文に組織固有の規約まで入れると、肥大して保守できなくなります。カスタムのサブエージェントはCLAUDE.mdを初期コンテキストに読み込むので、規約はそちらに置けます。
## Salesforce Flow の規約
- Flow は force-app/main/default/flows/ に置く
- ループ内でレコード操作をしない。コレクション変数に集めて外で1回
- 更新・作成・削除・取得・アクション呼び出しには faultConnector を付ける
- レコードIDを値に直書きしない。必要なら Get Records か変数で取る対話ではなく、スクリプトから呼ぶ手もあります。非対話モードの claude -p は、--agents にサブエージェント定義のJSONファイルのパスを受け取れます。ファイルで渡す形はClaude Code v2.1.281以降が必要で、対話セッションではファイルパスが拒否されます。たとえば claude -p --agents ./agents.json "変更したFlowをflow-metadata-reviewerで点検して" の形です。出力の形式を固定したいときは、指示文の出力見出しを揃えておきます。
点検できないこと
サブエージェントが読むのはリポジトリの中のXMLだけです。組織の中で別のバージョンが有効になっている場合、リポジトリと違う内容が実際には動いています。レビューの前に、対象のバージョンが有効なバージョンと一致しているかを人が確かめます。
また、ループの周回数はXMLからは分かりません。コレクションが数件に決まっているなら、ループ内のDMLが実害にならないこともあります。報告に「周回するコレクションが何に由来するか」を書かせたいときは、指示文に1行足します。Apexで同じ型の点検を組んだ手順は、Claude CodeでApexのガバナ制限違反を探して一括処理に直すにあります。
同じ「読み取り専用のサブエージェントに観点を渡す」構成は、認証コードの点検にも使えます。観点の書き方はClaude CodeにJWT検証コードをレビューさせると共通で、観点別にサブエージェントを分ける方法はClaude Codeでdesign docをレビューさせるが詳しいです。
まとめ
flow-meta.xml の点検は、ループ内DML、faultパス欠落、ハードコードIDの3点をXMLのタグに対応づけると、サブエージェントへの指示に落とせます。tools を Read, Grep, Glob に絞った読み取り専用で定義し、RecordBeforeSave の更新を除くといった例外まで書いておくと、報告がそのまま読めます。
CIでの合否はスキャナーに、変更の意図を踏まえた指摘はサブエージェントに任せる分担にすると、どちらか片方だけのときより見落としが減ります。ClaudeからSalesforceのデータを操作する別の連携は、ClaudeとSalesforceの連携方法にあります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。