grafana-best-practice — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited grafana-best-practice (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill to make Grafana dashboards readable, correctly aggregated, navigable, and safely deployable. Treat the rendered dashboard, Grafana API state, datasource query results, and container health as the source of truth.
uid, title, tags, templating.list, links, panels.provisioning/dashboards/*.yaml, especially folder, provider path, and allowUiUpdates./api/search, /api/folders, /api/dashboards/uid/<uid>, /api/ds/query, /api/health.symbol, reason, side in a trading app), container_name for containers, and host-level metrics that carry none of these.environment, project, url, or raw Prometheus family names.Prefer a named Grafana folder when permissions are known to be handled. For provisioned Grafana OSS dashboards with anonymous access, verify folder permissions explicitly because named folders can 403 even when dashboards exist.
Safe folder checklist:
provisioning/dashboards/default.yaml uses the intended folder, for example folder: Trader./api/folders shows the folder./api/search?type=dash-db&tag=<base-tag> shows every dashboard with the expected folderTitle.200 for both /d/<uid>/... and /dashboards.Use one stable base tag for dashboard links and discovery, then add semantic tags. Keep tags low-cardinality and queryable.
Recommended tag shape — one stable base tag (the service slug) plus semantic axes. The values below are an illustrative set from a trading app; substitute your own facets:
<service> # stable base tag, e.g. `trader`
area:<facet> # e.g. area:overview | area:trading | area:risk | area:infra
audience:<role> # e.g. audience:operator | audience:oncall
plane:<data-plane> # e.g. plane:prometheus | plane:trade | plane:hostKeep dashboard dropdown links on the stable base tag (the service slug, e.g. trader) so adding semantic tags does not break navigation.
Use explicit labels that explain important defaults:
{
"name": "instance",
"label": "Instance (All by default)",
"multi": true,
"includeAll": true,
"allValue": ".*"
}For high-level fleet dashboards, defaulting instance to All is good only if every panel handles All safely. When All is selected:
For Telegraf metric_version = 2, Prometheus metrics land as measurement prometheus, with the metric name as _field and each Prometheus label as a tag (the field name below, <metric_name>, is e.g. a trading app's trader_total_value_usdt):
from(bucket: v.defaultBucket)
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "prometheus")
|> filter(fn: (r) => r["instance"] =~ /${instance:regex}/)
|> filter(fn: (r) => r._field == "<metric_name>")Preserve the metric's real grouping label after renaming the field (here a per-instance/shard label):
|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)
|> set(key: "_field", value: "Total value")
|> keep(columns: ["_time", "_value", "_field", "instance"])
|> group(columns: ["instance", "_field"])Avoid collapsing multiple series into one unlabeled series:
|> keep(columns: ["_time", "_value"])
|> set(key: "_field", value: "total_value")
|> group(columns: ["_field"])Use each plane's true dimensions — they differ by data source:
_field.symbol, reason, side in a trading app) only when the panel intentionally aggregates across the selected instances.instance/shard label the measurement doesn't have. Host CPU/memory/disk are usually fleet/host-level.container_name.When using map() to turn labels into friendly series names, finish with a bounded keep/group (this example maps a trading app's exit-reason label):
|> map(fn: (r) => ({ r with _field:
if r.reason == "stop_loss" then "Stop-loss"
else if r.reason == "take_profit" then "Take-profit"
else "Other" }))
|> keep(columns: ["_time", "_value", "_field", "instance"])
|> group(columns: ["instance", "_field"])Stat and gauge panels:
fieldConfig.defaults.displayName to a short value, often ${__field.labels.instance} for per-instance cards.last() when the panel only needs the current value.trader_total_pnl_usdt {environment="...", url="..."} to render in compact cards.Time series panels:
"legend": {
"displayMode": "table",
"placement": "right",
"calcs": ["last", "max"],
"sortBy": "Last",
"sortDesc": true
}min.${__field.labels.instance} ${__field.name} or ${__field.labels.container_name}.Bar charts:
_value.When configuring or debugging provisioned alert rules, contact points, or notification delivery (Telegram chat_id / supergroup / topic message_thread_id, parse-mode escaping, live rule-health and delivery verification), read references/alerting.md.
When deploying dashboards/alerts, bumping the Grafana major version, running the minimum post-deploy verification curls, or wiring the compose + Telegraf monitoring stack, read references/deployment.md.
When reviewing or finishing work, report evidence, not just intent:
/api/.../rules all ok) and a real delivery test, when alerting changed.If a validation failure appears, fix the dashboard and rerun the relevant static, API, and datasource checks before declaring completion.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.