plugins/home-assistant-dev/skills/ha-diagnostics/SKILL.md
Implement diagnostics for a Home Assistant integration. Required for Gold tier on the Integration Quality Scale. Use when asked about diagnostics.py, debug information, troubleshooting data, or redacting sensitive info.
npx skillsauth add l3digitalnet/claude-code-plugins ha-diagnosticsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Diagnostics provide users and developers with structured debug information while protecting sensitive data. Required for Gold tier on the Integration Quality Scale.
custom_components/{domain}/
├── __init__.py
├── diagnostics.py # Diagnostics implementation
└── ...
"""Diagnostics support for {Name}."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
# Keys to redact from diagnostics output
TO_REDACT = {
# Authentication
"password",
"token",
"api_key",
"access_token",
"refresh_token",
"secret",
"credentials",
# Personal information
"email",
"username",
"serial",
"serial_number",
"unique_id",
"mac",
"mac_address",
# Location
"latitude",
"longitude",
"location",
"address",
}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
return {
"config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
"coordinator_data": async_redact_data(
coordinator.data if coordinator.data else {}, TO_REDACT
),
}
"""Diagnostics support for {Name}."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
TO_REDACT = {
"password",
"token",
"api_key",
"access_token",
"refresh_token",
"secret",
"email",
"username",
"serial",
"mac",
"latitude",
"longitude",
}
# Additional keys in nested structures
TO_REDACT_NESTED = TO_REDACT | {"ip_address", "host", "ssid"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
# Get device and entity info
device_registry = dr.async_get(hass)
entity_registry = er.async_get(hass)
devices = []
for device in dr.async_entries_for_config_entry(device_registry, entry.entry_id):
entities = []
for entity in er.async_entries_for_device(
entity_registry, device.id, include_disabled_entities=True
):
entities.append({
"entity_id": entity.entity_id,
"disabled": entity.disabled,
"disabled_by": entity.disabled_by,
"platform": entity.platform,
})
devices.append({
"name": device.name,
"model": device.model,
"manufacturer": device.manufacturer,
"sw_version": device.sw_version,
"hw_version": device.hw_version,
"entities": entities,
})
return {
"config_entry": {
"entry_id": entry.entry_id,
"version": entry.version,
"domain": entry.domain,
"title": entry.title,
"data": async_redact_data(dict(entry.data), TO_REDACT),
"options": async_redact_data(dict(entry.options), TO_REDACT),
},
"devices": devices,
"coordinator": {
"last_update_success": coordinator.last_update_success,
"last_exception": str(coordinator.last_exception) if coordinator.last_exception else None,
"update_interval": str(coordinator.update_interval),
"data": async_redact_data(coordinator.data, TO_REDACT_NESTED) if coordinator.data else None,
},
}
For per-device diagnostics (useful for hub integrations):
async def async_get_device_diagnostics(
hass: HomeAssistant, entry: ConfigEntry, device: dr.DeviceEntry
) -> dict[str, Any]:
"""Return diagnostics for a device."""
coordinator = entry.runtime_data
# Find device data from coordinator
device_id = next(
(identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN),
None
)
device_data = coordinator.data.get(device_id, {}) if device_id else {}
return {
"device": {
"name": device.name,
"model": device.model,
"sw_version": device.sw_version,
},
"data": async_redact_data(device_data, TO_REDACT),
}
| Category | Keys to Redact | |----------|---------------| | Auth | password, token, api_key, access_token, refresh_token, secret, credentials, auth | | Personal | email, username, user_id, account_id | | Device IDs | serial, serial_number, unique_id, mac, mac_address | | Location | latitude, longitude, lat, lon, location, address, coordinates | | Network | ip_address, ip, ssid (consider context) |
The helper recursively redacts matching keys:
from homeassistant.components.diagnostics import async_redact_data
data = {
"device": {
"name": "Living Room",
"serial": "ABC123", # Will be redacted
"firmware": "1.2.3",
},
"auth": {
"token": "secret123", # Will be redacted
"expires": 3600,
}
}
redacted = async_redact_data(data, {"serial", "token"})
# Result:
# {
# "device": {"name": "Living Room", "serial": "**REDACTED**", "firmware": "1.2.3"},
# "auth": {"token": "**REDACTED**", "expires": 3600}
# }
For complex redaction needs:
def redact_url(url: str) -> str:
"""Redact auth tokens from URLs."""
import re
return re.sub(r'(token=)[^&]+', r'\1**REDACTED**', url)
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
coordinator = entry.runtime_data
# Custom redaction for URLs
api_url = redact_url(coordinator.client.base_url)
return {
"api_url": api_url,
"data": async_redact_data(coordinator.data, TO_REDACT),
}
"""Test diagnostics."""
from homeassistant.components.diagnostics import async_get_config_entry_diagnostics
from homeassistant.core import HomeAssistant
from custom_components.{domain} import DOMAIN
async def test_diagnostics(hass: HomeAssistant, mock_client) -> None:
"""Test diagnostics output."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
diagnostics = await async_get_config_entry_diagnostics(hass, entry)
# Verify structure
assert "config_entry" in diagnostics
assert "coordinator_data" in diagnostics
# Verify sensitive data is redacted
assert diagnostics["config_entry"]["data"]["password"] == "**REDACTED**"
ha-config-flowha-coordinatorha-quality-reviewdevelopment
Use when you're stuck or missing current information mid-task - the same command/API/approach failed twice, an error looks like a changed or deprecated API, or you need the current version of something, a fact from after your training cutoff, or to verify something you cannot confirm from the code in context. Starts with a cheap inline lookup and only escalates to a full research sweep if that fails. Do not use for routine pre-emptive checks before ordinary library work - for deliberate research, use /qdev:research.
documentation
Update Outline wiki documentation with implementation-level details from the current session by dispatching the up-docs-propagate-wiki sub-agent. This skill should be used when the user runs /up-docs:wiki.
documentation
Update repository documentation (README.md, docs/, CLAUDE.md) based on session changes by dispatching the up-docs-propagate-repo sub-agent. This skill should be used when the user runs /up-docs:repo.
documentation
Update Notion pages with strategic and organizational context from the current session by dispatching the up-docs-propagate-notion sub-agent. This skill should be used when the user runs /up-docs:notion.