Blog • Claude Code / OpenTelemetry
Nach dem Upgrade auf Claude Code 2.1.312 fehlt claude_code.safety_event in OpenTelemetry. Welche Audit-JSON-Felder ändern?
Am 27. Oktober 2026 lieferte Anthropic Claude Code 2.1.312. OpenTelemetry sendet claude_code.safety_event nicht mehr; 2.1.307 vom 21. Oktober hatte es für von permission rule, auto, safety check, Hook oder Sandbox gestoppte tool calls ergänzt.
Dieselbe Version setzt mcp_server.name bei Remote-MCP mit fester URL aus offiziellen Marketplace-Plugins auf den echten Servernamen statt custom. Dashboards mit safety_event-Zählung oder custom-Gruppierung alarmieren falsch ohne Versions-Gate im lokalen JSON.
Blockgründe und auto-Evaluationskontext stehen in Managed Agents auto-Evaluation; lokale permission-Queue in Agent permission policy; JSON für nicht-interaktives MCP-Timeout in MCP elicitation timeout. Dieser Artikel behandelt nur OpenTelemetry-Feld-Auslauf in 2.1.312, mcp_server.name-Abgleich und telemetry_gate-Verzweigung.
- Ab 2.1.312 sendet der Client kein claude_code.safety_event mehr; wer den Namen noch hört, soll unexpected_event melden.
- Bleibt pipeline count_safety_event und claudeCodeVersion ist 2.1.312, soll das Gate retire_counter liefern.
- Unter 2.1.312 soll marketplace Remote-MCP mcp_server.name wie github tragen; custom bei marketplacePlugin true → stale_mcp_alias.
- Laufen 2.1.307 und 2.1.312 gemischt, fehlt safety_event auf der alten Linie → missing_safety, nicht unexpected_event.
Zuerst dies: 2.1.312 ändert OpenTelemetry-Ereignisnamen und MCP-Span-Attribute, nicht permission rules. Unten: Changelog, ausgelaufenes safety_event-Beispiel, Pipeline-JSON und Schema, mcp_server.name-Vergleich, Gate-Tabelle, telemetry_gate-Quelltext, Config-Prüfung in JSONNote.
2.1.312 stoppt safety_event in OpenTelemetry und korrigiert Marketplace-MCP-Namen
Änderungen stehen in Claude Code changelog, Tag-Seite Release v2.1.312. Gegenüber 2.1.307: claude_code.safety_event entfällt ganz, und mcp_server.name ist bei Marketplace-MCP mit fester URL nicht mehr custom.
permission rule, Hooks und Sandbox blockieren weiter; nur der dedizierte safety_event-Export entfällt. Audits sollen outcome auf tool spans oder eigenes approval JSON lesen, nicht auf claude_code.safety_event warten.
Wie claude_code.safety_event in 2.1.307 aussah und warum 312 es nicht mehr gibt
2.1.307 packte blockierte Aufrufe in claude_code.safety_event mit tool.name, outcome blocked, source permission_rule. Beispiel nur für Legacy-Form; 2.1.312-Clients dürfen den Namen nicht mehr senden.
{
"name": "claude_code.safety_event",
"attributes": {
"tool.name": "Bash",
"outcome": "blocked",
"source": "permission_rule"
}
}
Erhält eine 312-Fleet das Ereignis noch, alte Binaries oder Collector-Replay prüfen. telemetry_gate soll unexpected_event liefern, wenn claudeCodeVersion mindestens 2.1.312 ist und span.name noch claude_code.safety_event heißt.
Wie Audit-Pipeline-JSON claudeCodeVersion und pipeline setzt
Jeden Export-Batch an claudeCodeVersion binden; pipeline deklariert safety_event-Zählung oder mcp_server.name-Gruppierung. count_safety_event auf 312 → retire_counter; bei Mix muss die Version vom Agent kommen.
{
"claudeCodeVersion": "2.1.312",
"pipeline": "count_safety_event",
"marketplacePlugin": true
}
additionalProperties und enums per JSON Schema fixieren. Bei Schema-Fehler zuerst schema zurückgeben, nicht ok oder retire_counter bei fehlenden Feldern raten.
{
"$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" }
}
}
Warum mcp_server.name bei Marketplace-Remote-MCP von custom zum Plugin-Namen wechselt
Ab 2.1.312 schreibt Remote-MCP aus offiziellen Anthropic-Marketplace-Plugins mit fester URL den echten Servernamen (z. B. github) auf tools/call-Spans statt custom. Self-hosted ausgenommen; custom bei marketplacePlugin true bedeutet veraltete Gruppierung.
{
"name": "tools/call",
"attributes": {
"mcp_server.name": "github",
"tool.name": "create_issue"
}
}
{
"name": "tools/call",
"attributes": {
"mcp_server.name": "custom",
"tool.name": "create_issue"
}
}
| Config und Span-Signal | telemetry_gate liefert | Operator-Aktion |
|---|---|---|
| claudeCodeVersion mindestens 2.1.312, pipeline weiter count_safety_event | retire_counter | safety_event-Zähler abschalten; stattdessen tool span outcome oder approval JSON. |
| Version mindestens 2.1.312, span.name noch claude_code.safety_event | unexpected_event | Alte Binaries oder Collector-Replay prüfen; nicht als normale Blocks zählen. |
| pipeline mcp_server_group, marketplacePlugin true, mcp_server.name noch custom | stale_mcp_alias | Grafana-Gruppen und Alert-Keys aktualisieren; custom auf echte Plugin-Namen mappen. |
| claudeCodeVersion unter 2.1.312, pipeline count_safety_event, Span ist kein safety_event | missing_safety | Alte Clients lassen safety_event aus; Zähler lockern oder nach Version bucketen, nicht mit unexpected_event vermischen. |
Wie telemetry_gate zwischen schema und fünf Fachzweigen wählt
Reihenfolge: config per Pipeline-JSON-Schema; span.name und attributes.mcp_server.name lesen; claudeCodeVersion mit 2.1.312 vergleichen. Auf 312 safety_event-Name → unexpected_event; 312 mit count_safety_event → retire_counter; unter 312 count-Pipeline, Span nicht safety_event → missing_safety; 312, mcp_server_group, marketplacePlugin, custom → stale_mcp_alias; sonst ok.
retire_counter und ok nicht zu einem passed-Bit zusammenlegen. Getrennt zählen, um ungeänderte pipeline-Felder und hängende custom-Gruppierung zu sehen.
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"
Wie validiert man Pipeline-JSON und Span-Beispiele in JSONNote
- 1Pipeline-Config per Schema fixieren
Öffnen Sie JSON Schema. Pipeline-Schema ins erste Feld, claudeCodeVersion und pipeline ins zweite. Enum- oder Pattern-Fehler zuerst; bei schlechter Form keine Fachzweige.
- 2Format für Span-Attribute
Öffnen Sie JSON formatieren, tools/call-Span vom Collector einfügen. Prüfen, ob mcp_server.name github oder custom ist, bevor stale_mcp_alias gewählt wird.
- 3Diff: MCP-Name vorher und nachher
Öffnen Sie JSON Diff. Links 2.1.312-Span, rechts Legacy mit custom. Hervorgehobenes mcp_server.name ist die Grafana-Zeile zum Anpassen.
Häufige Fragen
Sendet 2.1.312 Blocks unter einem anderen OpenTelemetry-Ereignisnamen?
Das Changelog stoppt nur claude_code.safety_event; kein Ersatz-Ereignis ist zugesagt. Verlassen Sie sich auf tool spans, permission logs oder approval JSON, nicht auf einen neuen safety-Namen.
Kann pipeline auf 2.1.312 count_safety_event bleiben?
Das Feld darf als Migrationsmarkierung bleiben, aber telemetry_gate liefert auf 312 retire_counter, weil der Zähler nie Ereignisse sieht. pipeline enum ändern oder auf tool span outcome wechseln, retire_counter nicht als ok behandeln.
Zeigt Self-hosted-MCP weiter mcp_server.name custom?
stale_mcp_alias gilt hier nur für Remote-MCP mit fester URL aus offiziellen Marketplace-Plugins bei marketplacePlugin true. Self-hosted-Namen stehen außerhalb dieses 2.1.312-Changelogs; nicht jedes custom ist Fehlkonfiguration.
Wie trennt man missing_safety und unexpected_event bei Mix aus 2.1.307 und 2.1.312?
unexpected_event: Config mindestens 2.1.312, claude_code.safety_event kommt noch. missing_safety: Config unter 312, count_safety_event-Pipeline, Span ist nicht das Ereignis. Beides braucht claudeCodeVersion, nicht nur Collector-Gesamtzahl.
Bedeutet telemetry_gate ok, dass MCP-Namensänderungen ignoriert werden können?
ok heißt nur: dieses Config- und Span-Beispiel bestand alle Regeln, z. B. mcp_server_group mit echtem Plugin-Namen oder ohne count_safety_event. Keine Garantie, dass künftige Batches keine Legacy-Spans enthalten; weiter sampeln.
Fazit
Claude Code 2.1.312 entfernt den dedizierten OpenTelemetry-safety_event-Export und richtet mcp_server.name bei Marketplace-Remote-MCP auf echte Plugin-Namen aus. Audit-JSON braucht claudeCodeVersion und pipeline, damit telemetry_gate bei gemischten Flotten verzweigen kann.
Entscheidung: claudeCodeVersion mindestens 2.1.312 mit claude_code.safety_event → unexpected_event; gleiche Version mit count_safety_event → retire_counter und Metriken ändern; unter 2.1.312 safety_event zählen, Span nicht das Ereignis → missing_safety; auf 312 marketplacePlugin true und mcp_server.name custom → stale_mcp_alias; Schema scheitert → schema zuerst; sonst ok.
Als Nächstes: Pipeline-JSON per Schema, Format für Span-Attribute, Diff für mcp_server.name, telemetry_gate-Rückgaben getrennt berichten.