ブログ • Claude Code / OpenTelemetry
Claude Code 2.1.312 に上げたあと OpenTelemetry に claude_code.safety_event が出なくなる。監査 JSON のどのフィールドを直すか?
2026年10月27日 Anthropic は Claude Code 2.1.312 を出した。OpenTelemetry 出力は claude_code.safety_event を送らなくなった。10月21日の 2.1.307 で permission rule、auto、safety check、hook、sandbox で止まった tool call 向けに追加されていた。
同版で公式 marketplace プラグインの固定 URL リモート MCP の span 属性 mcp_server.name が custom から実サーバ名に変わる。safety_event カウントや custom グループの Grafana は更新後に誤報しうる。ローカル JSON に版门槛が要る。
ブロック理由と auto 評価の文脈は Managed Agents auto 評価。ローカル permission キューは Agent permission policy。非対話 MCP タイムアウト JSON は MCP elicitation timeout。本記事は 2.1.312 の OpenTelemetry フィールド退役と mcp_server.name 整合、telemetry_gate 分岐だけを書く。
- 2.1.312 からクライアントは claude_code.safety_event を出さない。まだその名前を聞くなら unexpected_event。
- pipeline が count_safety_event のまま claudeCodeVersion が 2.1.312 なら、ゲートは retire_counter でカウンタ退役を促す。
- 2.1.312 では marketplace リモート MCP の mcp_server.name は github 等になる。custom のまま marketplacePlugin true なら stale_mcp_alias。
- 2.1.307 と 2.1.312 混在では、旧系で safety_event が無いのは missing_safety で、unexpected_event と混同しない。
先にこれだけ:2.1.312 が変えるのは OpenTelemetry イベント名と MCP span 属性であり permission rule 自体ではない。以下は changelog、退役 safety_event サンプル、流水线 JSON と schema、mcp_server.name 比較、ゲート表、telemetry_gate 源码、JSONNote での設定検証。
2.1.312 は OpenTelemetry で safety_event を止め marketplace MCP 名を修正
変更は Claude Code changelog、タグは v2.1.312 release。2.1.307 比で telemetry の目立つ変更は claude_code.safety_event 全停止と、marketplace 固定 URL MCP で mcp_server.name を custom にしないこと。
permission rule、hook、sandbox は tool call を止めるが、専用 safety_event 出力だけが無くなる。監査は既存 tool span の outcome か自前 approval JSON を見て、claude_code.safety_event の再出現を待たない。
2.1.307 の claude_code.safety_event と 312 で消える理由
2.1.307 は permission rule、auto、safety check、hook、sandbox で止めた呼び出しを claude_code.safety_event に包んだ。tool.name、outcome blocked、source permission_rule 等。下は旧形状对照用で、312 クライアントは同名を出さない。
{
"name": "claude_code.safety_event",
"attributes": {
"tool.name": "Bash",
"outcome": "blocked",
"source": "permission_rule"
}
}
312 クラスタでまだ受信するなら旧バイナリ混在か collector の旧バッチ再生を疑う。claudeCodeVersion が 2.1.312 以上で span.name が claude_code.safety_event なら unexpected_event とし、通常ブロック计数にしない。
監査流水线 JSON の claudeCodeVersion と pipeline
各エクスポート批次を claudeCodeVersion に結び、pipeline で safety_event 计数か mcp_server.name 分组かを宣言する。312 で count_safety_event のままなら retire_counter。混在時は版は agent 自報から。
{
"claudeCodeVersion": "2.1.312",
"pipeline": "count_safety_event",
"marketplacePlugin": true
}
JSON Schema で additionalProperties と enum を固定し pipeline の typo を防ぐ。schema 失敗時は telemetry_gate は先に schema を返し、欠落フィールドで ok や retire_counter を当てない。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["claudeCodeVersion", "pipeline"],
"properties": {
"claudeCodeVersion": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
"pipeline": { "enum": ["count_safety_event", "mcp_server_group"] },
"marketplacePlugin": { "type": "boolean" }
}
}
marketplace リモート MCP の mcp_server.name が custom から插件名になる理由
2.1.312 から公式 Anthropic marketplace プラグインの固定 URL リモート MCP は tools/call span に github 等の実名を書き、custom を使わない。自前ホスト命名は対象外。marketplacePlugin true で custom が残るなら分组规则が古い。
{
"name": "tools/call",
"attributes": {
"mcp_server.name": "github",
"tool.name": "create_issue"
}
}
{
"name": "tools/call",
"attributes": {
"mcp_server.name": "custom",
"tool.name": "create_issue"
}
}
| 設定と span 現象 | telemetry_gate 戻り | 運用対応 |
|---|---|---|
| claudeCodeVersion が 2.1.312 以上で pipeline が count_safety_event のまま | retire_counter | safety_event カウンタを外し、tool span outcome か approval JSON へ。 |
| 版 2.1.312 以上なのに span.name が claude_code.safety_event | unexpected_event | 旧バイナリ混在か collector 再生を調べ、通常ブロックに数えない。 |
| pipeline が mcp_server_group、marketplacePlugin true、mcp_server.name が custom のまま | stale_mcp_alias | Grafana 分组とアラート键を更新し custom を実插件名に置換。 |
| claudeCodeVersion が 2.1.312 未満、pipeline count_safety_event、span が safety_event でない | missing_safety | 旧系クライアントは safety_event を出さないことがある。计数を緩めるか版で分桶し unexpected_event と合算しない。 |
telemetry_gate は schema と五业务分支をどう選ぶか
順序:流水线 JSON Schema で config を検証;span.name と attributes.mcp_server.name を読む;claudeCodeVersion を 2.1.312 と比較。312 で safety_event 名 → unexpected_event;312 かつ count_safety_event → retire_counter;312 未満で count なのに span が safety_event でない → missing_safety;312、mcp_server_group、marketplacePlugin、custom → stale_mcp_alias;それ以外 ok。
retire_counter と ok を passed 一つに畳まない。レポートで分けて数え、pipeline 未更新チームと MCP 分组が custom のままかを見る。
def actual_type(data: object) -> str:
if data is None:
return "null"
if isinstance(data, bool):
return "boolean"
if isinstance(data, list):
return "array"
if isinstance(data, int):
return "integer"
if isinstance(data, float):
return "number"
if isinstance(data, str):
return "string"
if isinstance(data, dict):
return "object"
raise TypeError(type(data))
def validate(data: object, schema: dict) -> list[str]:
errors: list[str] = []
if not isinstance(schema, dict):
return errors
types = schema.get("type")
if isinstance(types, str):
types = [types]
actual = actual_type(data)
if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
errors.append("type")
if "enum" in schema and not any(value == data for value in schema["enum"]):
errors.append("enum")
if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
errors.append("minLength")
if isinstance(data, dict) and schema.get("properties"):
for key in schema.get("required") or []:
if key not in data:
errors.append(f"required:{key}")
if schema.get("additionalProperties") is False:
for key in data:
if key not in schema["properties"]:
errors.append(f"additional:{key}")
for key, sub in schema["properties"].items():
if key in data and data[key] is not None:
errors.extend(validate(data[key], sub))
return errors
def parse_version(text: str) -> tuple[int, int, int]:
parts = text.split(".")
if len(parts) != 3:
return (0, 0, 0)
try:
return (int(parts[0]), int(parts[1]), int(parts[2]))
except ValueError:
return (0, 0, 0)
def telemetry_gate(config: dict, span: dict, schema: dict) -> str:
if validate(config, schema):
return "schema"
version = parse_version(str(config.get("claudeCodeVersion", "0.0.0")))
attrs = span.get("attributes") if isinstance(span.get("attributes"), dict) else {}
name = span.get("name")
if version >= (2, 1, 312) and name == "claude_code.safety_event":
return "unexpected_event"
if version >= (2, 1, 312) and config.get("pipeline") == "count_safety_event":
return "retire_counter"
if version < (2, 1, 312) and config.get("pipeline") == "count_safety_event" and name != "claude_code.safety_event":
return "missing_safety"
if (
version >= (2, 1, 312)
and config.get("pipeline") == "mcp_server_group"
and config.get("marketplacePlugin")
and attrs.get("mcp_server.name") == "custom"
):
return "stale_mcp_alias"
return "ok"
JSONNote で流水线 JSON と span サンプルをどう検証するか
- 1Schema で流水线設定を固定
開く JSON Schema。1欄に流水线 schema、2欄に claudeCodeVersion と pipeline 設定。enum や pattern 誤りが先に赤くなり、形が壊れた時点で业务分支を返さない。
- 2フォーマットで span 属性を確認
開く JSON フォーマット。collector から抜いた tools/call span を貼る。mcp_server.name が github か custom かを見て stale_mcp_alias 分支を選ぶ。
- 3Diff で MCP 名前の前後
開く JSON Diff。左に 2.1.312 新 span、右に custom の旧サンプル。Diff で光る mcp_server.name が Grafana 分组の修正行。
よくある質問
2.1.312 はブロックを別 OpenTelemetry イベント名で出すか?
changelog は claude_code.safety_event 停止のみで、1対1の代替イベントは約束していない。既存 tool span、permission ログ、自前 approval JSON に頼り、新 safety イベント名を待たない。
2.1.312 で pipeline を count_safety_event のままにできるか?
フィールドは移行标记として残せるが、312 では telemetry_gate が retire_counter を返し、カウンタはイベントを待てない。pipeline enum を変えるか tool span outcome 指標へ移し、retire_counter を ok として黙認しない。
自前ホスト MCP でも mcp_server.name が custom か?
本篇の stale_mcp_alias は公式 marketplace プラグインで marketplacePlugin true の固定 URL リモート MCP のみ。自前ホスト命名は 2.1.312 changelog 外。すべての custom を misconfig としない。
2.1.307 と 2.1.312 混在で missing_safety と unexpected_event はどう分けるか?
unexpected_event は設定が 2.1.312 以上なのに claude_code.safety_event が来る場合。missing_safety は設定が 312 未満で pipeline が count_safety_event なのに span がそのイベントでない場合。どちらも claudeCodeVersion 依存で、collector 総量だけでは分けられない。
telemetry_gate が ok なら MCP 名変更を無視していいか?
ok はこの config と span サンプルが全规则を通っただけ。例:mcp_server_group で実插件名を見た、count_safety_event を外した。将来批次に旧 span が混ざらない保証ではない。継続抽样が要る。
まとめ
Claude Code 2.1.312 は OpenTelemetry の safety_event 専用出力をやめ、marketplace リモート MCP の mcp_server.name を実插件名に揃えた。監査 JSON は claudeCodeVersion と pipeline を明記し、混在と移行期に telemetry_gate が実行可能な分支を返すようにする。
結論:claudeCodeVersion が 2.1.312 以上で claude_code.safety_event → unexpected_event。同版で pipeline が count_safety_event → retire_counter と指標変更。312 未満で safety_event 计数なのに span が該当でない → missing_safety。312 で marketplacePlugin true かつ mcp_server.name が custom → stale_mcp_alias。schema 不通 → schema 優先。それ以外 ok。
次:Schema で流水线 JSON を固定し、フォーマットで span 属性を見て、Diff で mcp_server.name 前後を比べ、telemetry_gate 戻り値をレポートで分ける。