Claude CodeでUpdate Setの衝突を点検する — ServiceNowの取り込み前に
Update SetのXMLを本番に取り込む前に、同じレコードを触る別セットとの上書きと、開発環境の値の混入をClaude Codeで点検する手順です。
Update Setを本番へ入れる前の点検は、2つの問いに分けられます。同じレコードを触る別のセットがないか。開発環境の値が混ざっていないか。この2つはXMLを機械的に読めば拾えるので、Claude Codeを「読んで判断する側」に置き、集計はスクリプトに任せる構成が扱いやすくなります。
ここでは、XMLから重複と環境固有の値をCSVに落とすスクリプトを作り、そのCSVだけをClaude Codeの非対話モード(claude -p)へ渡す手順を示します。ServiceNowのプレビュー機能が見る範囲と、この点検が補う範囲の線引きも書きます。
プレビューが見るものと、見ないもの
ServiceNowのプレビューは、取得済みのUpdate Setを「ローカルのインスタンス」と突き合わせて問題を探す機能です。衝突(collision)の定義は、新しいローカル更新があるアップデートのことです。判定は、各カスタマー更新レコードのNameとUpdatedの値の比較で行われ、名前が同じで更新日時が違えば衝突になります。
プレビュー画面の問題一覧には、次の種類が並びます。
| 問題の種類 | 意味 |
|---|---|
| Missing Object | 意味参照先のオブジェクトが移行先にない |
| Collision | 意味セット内の変更が、ローカルの同一オブジェクトの変更より古い |
| Uncommitted update | 意味同じオブジェクトが、未コミットの別のリモートセットにある |
| Table to be deleted has data | 意味削除するテーブルにデータが残っている |
| Application scope validation issue | 意味アプリケーションのスコープが移行先で検証できない |
| Conflict within a single batch | 意味同じバッチ内の複数セットで変更が競合する |
この一覧に「開発環境のURLやメールアドレスが残っている」という種類はありません。プレビューは構造上の不整合を見る機能で、値の中身の吟味は対象外です。ここがXMLを事前に読む意味になります。
もう1つの違いは、点検するタイミングです。プレビューは、セットを取得(またはXMLから読み込み)したあとに、移行先のインスタンスの上で動きます。XMLの段階で、これから入れる複数のセットを横に並べて「同じレコードを複数のセットが触っていないか」を見ておけば、プレビューで初めて気づく手戻りを減らせます。
Claude Codeに渡す範囲を決める
Update SetのXMLには、スクリプトの全文やシステムプロパティの値が入ります。そのままプロンプトに流すより、スクリプトで必要な列だけを抜き出し、抜き出した結果をClaude Codeに渡す形のほうが、渡す情報を絞れます。
非対話モードは標準入力を読めるので、CSVをパイプで渡せます。公式のビルドスクリプト例も、差分をパイプで渡す理由として「Claudeに差分を読むためのBashの権限が要らなくなる」ことを挙げています。この点検でも同じ形にすると、Claude Codeにはファイルを読む権限も実行権限も要りません。
標準入力の上限は10MBです。超えるとエラーで終了するため、大きなセットを束ねるときはCSVの列を絞るか、セットごとに分けて実行します。
手順
XMLの点検から結果の判定まで
- 1
Update SetをXMLで書き出す
ソース側のインスタンスで、状態が「Complete」のセットを開き、関連リンクの「Export to XML」で保存します。取得済み(Retrieved)のセットは、Loaded状態であれば同じ方法で書き出せます。保存したファイルを1つのディレクトリ(以下の例では
sets/)にまとめます。 - 2
スクリプトで重複と環境固有の値を抜き出す
次の節のスクリプトを実行し、CSVを作ります。
- 3
CSVをClaude Codeに渡して要点をまとめさせる
読み取り専用の設定で
claude -pを実行し、JSONで結果を受け取ります。 - 4
プレビューで移行先との突き合わせをする
点検で見つかった項目を直したうえで、本番でセットを読み込み、プレビューの問題一覧を確認します。
XMLから重複と環境値を抜き出すスクリプト
Update SetのXMLの要素構成は、ServiceNowのドキュメントには記載がありません。次のスクリプトは、sys_update_xml 要素が name・target_name・type・action・payload を持つ形を前提にしています。手元のXMLと要素名が違うときは、findtext の引数を合わせてください。
import csv, glob, re, sys
import xml.etree.ElementTree as ET
from collections import defaultdict
# 環境固有らしい値の目印(例。自社の命名に合わせて足す)
ENV = re.compile(
r"[\w.-]+\.service-now\.com|https?://[^\s'\"<]+"
r"|[\w.+-]+@[\w-]+\.[\w.]+|\b\d{1,3}(?:\.\d{1,3}){3}\b"
)
rows = []
for path in sorted(glob.glob(sys.argv[1] + "/*.xml")):
root = ET.parse(path).getroot()
set_name = root.findtext("sys_remote_update_set/name")
for u in root.iter("sys_update_xml"):
payload = u.findtext("payload") or ""
rows.append({
"set": set_name, "name": u.findtext("name"),
"target": u.findtext("target_name"),
"type": u.findtext("type"),
"updated": u.findtext("sys_updated_on"),
"env": ";".join(sorted(set(ENV.findall(payload)))),
})
by_name = defaultdict(list)
for r in rows:
by_name[r["name"]].append(r)
w = csv.writer(sys.stdout)
w.writerow(["kind", "type", "target", "detail"])
for name, rs in by_name.items():
if len({r["set"] for r in rs}) > 1:
detail = " | ".join(f'{r["set"]}@{r["updated"]}' for r in rs)
w.writerow(["overlap", rs[0]["type"], rs[0]["target"], detail])
for r in rows:
if r["env"]:
w.writerow(["env_value", r["type"], r["target"],
f'{r["set"]}: {r["env"]}'])重複の判定は、更新レコードの name が複数のセットに現れるかどうかだけです。ServiceNowの衝突判定がNameとUpdatedを見るのと同じ軸で、セット間の重なりを先に拾います。name の末尾には、多くのレコードでsys_idが入ります。作成時に決まるsys_idが名前に入るため、別のインスタンスで同じ内容のレコードを別々に作っても、名前は一致しません。この点検が拾えるのは、同じレコードを別のセットで更新している場合に限られます。
作例で動かした結果
2つのセットで同じScript Include(NotifyUtil)を更新し、開発環境のURLとメールアドレスを入れた作例のXMLを用意して実行しました。XMLは動作確認のために作った例で、実在のインスタンスのものではありません。
python3 -I us_scan.py sets | tee report.csvkind,type,target,detail
overlap,Script Include,NotifyUtil,INC-1001 通知ルール改修@2026-10-10 09:12:44 | INC-1002 通知の文言修正@2026-10-12 15:03:10
env_value,Script Include,NotifyUtil,INC-1001 通知ルール改修: https://acme-dev.service-now.com/api/x_acme/notify
env_value,System Property,acme.notify.endpoint,INC-1001 通知ルール改修: https://acme-dev.service-now.com/api
env_value,Business Rule,Notify on close,INC-1002 通知の文言修正: ops-dev@example.comNotifyUtil は2つのセットの両方に入っていて、更新日時が違います。どちらを先に入れても、あとから入れたほうの内容が残る可能性があります。ServiceNowの説明では、プレビューレコードの提案アクション(Proposed action)の既定値は、移行先に新しい更新があってもCommit(取り込む)です。放置すると、片方の変更が気づかないうちに消えます。
読み取り専用でClaude Codeに判断させる
CSVを claude -p に渡します。ここでの目的は、行を人間が優先順位を付けて読めるように整理することです。まず、点検用ディレクトリの .claude/settings.json に、使えるツールを絞る設定を置きます。次の実行はそのディレクトリで行います。
{
"permissions": {
"deny": ["Bash", "Edit", "Write", "WebFetch"]
}
}ツール名だけの拒否ルールは、そのツールをClaudeのコンテキストから取り除きます。Bashを拒否すると、コマンドを実行する経路そのものがなくなります。拒否は許可より先に評価され、あとから許可ルールを足しても拒否は覆りません。実行は次のとおりです。
cat report.csv | claude -p \
"Update Setの点検CSVを読み、移行前に人が決めるべき項目を、
重大なものから順に挙げて。overlapは上書きされる側の内容が失われる
可能性、env_valueは本番で書き換えが要る値かどうかを判断する。
CSVに無い情報は推測しない。" \
--permission-mode dontAsk \
--output-format json \
--json-schema '{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"target":{"type":"string"},"kind":{"type":"string"},"severity":{"type":"string"},"reason":{"type":"string"}},"required":["target","kind","severity","reason"]}}},"required":["findings"]}' \
| jq '.structured_output.findings[]'dontAsk は、承認を求める操作を拒否するモードです。無人の実行で、想定外のツール呼び出しを通さないために向いています。--json-schema を付けると、結果は structured_output に入ります。構造化出力が得られなかった場合は、このフィールド自体が付かないので、後続の処理では存在を確かめてから使います。
返ってくる内容は実行のたびに表現が変わります。次のような形になるのが期待される姿で、実際の文面はその場の出力を見て判断してください。
NotifyUtilが2つのセットにまたがる。どちらの内容を残すか、INC-1001とINC-1002の担当者に確認が要る(重大度は高)acme.notify.endpointの値が開発環境のURL。本番の値に差し替えるか、本番側のレコードを残す設定が要るNotify on closeの宛先が開発用のメールアドレス
判定の根拠はCSVの1行だけです。Claude Codeはスクリプトの本文を見ていないので、「本当に内容が食い違うか」までは分かりません。重複の行が出たら、該当レコードのpayloadを2つ並べて差分を取り、そこで初めて採用する側を決めます。
衝突が見つかったあとの片付け方
重複した行が実際の衝突だったときの手段は、ServiceNowの手順に沿います。ローカルのセット同士なら、Local Update Setsで両方を選び、Actionsから「Compare Update Sets」を実行すると、衝突レポートが作られます。同じ衝突番号が別々のセットに並ぶ行が、同じ変更を指します。不要なほうの更新レコードを削除し、もう一度比較して、衝突が消えたことを確かめます。
ここに落とし穴が2つあります。
落とし穴
更新レコードを消しても、変更自体は取り消されない
更新レコードの削除は、カスタマイズの記録を消すだけで、カスタマイズ自体は元に戻りません。また、レコードは衝突の一覧の削除アクションではなく、更新レコードを直接開いて削除します。
もう1つは、取り込み済みのセットを取り消す場合です。Back Outは削除の更新を現在のセットに加えるため、一度コミットして取り消し、再度適用すると、直近の削除が衝突を起こしてプレビューのエラーになります。取り消しの操作で変更の一部がデータ損失につながる場合もあるため、問題が出そうなときは、取り消すのではなく、変更を打ち消す新しいセットを作ってコミットするのが、ドキュメントの案内です。
運用に組み込むときの注意
- XMLの読み込みには
security_adminロールへの昇格が要ります。点検用のスクリプトは読み取りだけなので、インスタンスの認証情報は不要です - 自動で走らせるときは、CIの定期実行などで
claude -pを呼び、--permission-prompts noneを足します。承認できる人がいない場面で、承認待ちのまま止まる状況を避けられます。このフラグはClaude Code v2.1.259以降で使えます ENVの正規表現は例です。社内のドメイン名や、本番で必ず置き換えるsys_propertiesの名前を足すほど、拾える精度が上がります- 別々に作った同一内容のレコードは、前述のとおり名前が一致しないため、この点検では拾えません。重複を防ぐには、セットで運ぶか、XMLのエクスポート・インポートでsys_idを揃えます
SDKでアプリを作る側の流れはClaude CodeでServiceNow SDKのFluentアプリを作るにまとめています。権限を読み取りだけに絞り、claude -p に判断させる同じ考え方は、Claude CodeにCache-Controlを点検させるでも使っています。スクリプトで一覧を作ってから読ませる型は、IFCの属性の抜けを点検する手順が近い例です。ServiceNowの問い合わせ対応側の自動化はServiceNowをClaude CodeやCoworkで使う手順が扱っています。
まとめ
Update Setの事前点検は、集計をスクリプトに、整理をClaude Codeに分けると、渡す情報も権限も小さく保てます。プレビューは移行先との不整合を見て、開発環境の値の混入は見ません。この空白をXMLの段階で埋めておくと、本番で読み込んだあとのプレビューは、Missing Objectなど移行先に依存する項目に集中できます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/headless
- code.claude.com/docs/en/permissions
- servicenow.com/docs/r/application-development/system-update-sets/update-s…
- servicenow.com/docs/r/application-development/system-update-sets/t_Previe…
- servicenow.com/docs/r/application-development/system-update-sets/t_Compar…
- servicenow.com/docs/r/application-development/system-update-sets/t_SaveAn…