Claude Media
Claude CodeでUpdate Setの衝突を点検する — ServiceNowの取り込み前に

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. 1

    Update SetをXMLで書き出す

    ソース側のインスタンスで、状態が「Complete」のセットを開き、関連リンクの「Export to XML」で保存します。取得済み(Retrieved)のセットは、Loaded状態であれば同じ方法で書き出せます。保存したファイルを1つのディレクトリ(以下の例では sets/)にまとめます。

  2. 2

    スクリプトで重複と環境固有の値を抜き出す

    次の節のスクリプトを実行し、CSVを作ります。

  3. 3

    CSVをClaude Codeに渡して要点をまとめさせる

    読み取り専用の設定で claude -p を実行し、JSONで結果を受け取ります。

  4. 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.csv
kind,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.com

NotifyUtil は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など移行先に依存する項目に集中できます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. code.claude.com/docs/en/permissions
  3. servicenow.com/docs/r/application-development/system-update-sets/update-s…
  4. servicenow.com/docs/r/application-development/system-update-sets/t_Previe…
  5. servicenow.com/docs/r/application-development/system-update-sets/t_Compar…
  6. servicenow.com/docs/r/application-development/system-update-sets/t_SaveAn…
この記事を共有:XはてブLinkedIn