ブログ • 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 が変えるのは 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 クライアントは同名を出さない。

2.1.307 時代の claude_code.safety_event 形状(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 自報から。

流水线設定:版 2.1.312、pipeline と marketplace フラグ。
{
  "claudeCodeVersion": "2.1.312",
  "pipeline": "count_safety_event",
  "marketplacePlugin": true
}

JSON Schema で additionalProperties と enum を固定し pipeline の typo を防ぐ。schema 失敗時は telemetry_gate は先に schema を返し、欠落フィールドで ok や retire_counter を当てない。

流水线 JSON Schema:形を通してから telemetry_gate。
{
  "$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 が残るなら分组规则が古い。

2.1.312:mcp_server.name は marketplace 実名。
{
  "name": "tools/call",
  "attributes": {
    "mcp_server.name": "github",
    "tool.name": "create_issue"
  }
}
2.1.311 以前:固定 URL marketplace MCP は custom が多い。
{
  "name": "tools/call",
  "attributes": {
    "mcp_server.name": "custom",
    "tool.name": "create_issue"
  }
}
設定と span 現象telemetry_gate 戻り運用対応
claudeCodeVersion が 2.1.312 以上で pipeline が count_safety_event のままretire_countersafety_event カウンタを外し、tool span outcome か approval JSON へ。
版 2.1.312 以上なのに span.name が claude_code.safety_eventunexpected_event旧バイナリ混在か collector 再生を調べ、通常ブロックに数えない。
pipeline が mcp_server_group、marketplacePlugin true、mcp_server.name が custom のままstale_mcp_aliasGrafana 分组とアラート键を更新し 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 のままかを見る。

telemetry_gate:schema、unexpected_event、retire_counter、missing_safety、stale_mcp_alias、ok。
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 サンプルをどう検証するか

  1. 1
    Schema で流水线設定を固定

    開く JSON Schema。1欄に流水线 schema、2欄に claudeCodeVersion と pipeline 設定。enum や pattern 誤りが先に赤くなり、形が壊れた時点で业务分支を返さない。

  2. 2
    フォーマットで span 属性を確認

    開く JSON フォーマット。collector から抜いた tools/call span を貼る。mcp_server.name が github か custom かを見て stale_mcp_alias 分支を選ぶ。

  3. 3
    Diff で 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 戻り値をレポートで分ける。

ブログへ戻る