Updated June 2026Cookbook19 min read

Claude grafana skill: 10 dashboards you can ship as code

Ten real Grafana dashboards — service health, p95 latency, multi-service templating, an alert rule, a Loki log panel, provisioned dashboard-as-code, repeated per-instance rows, an SLO burn-rate board, a re-themed community import, and deploy annotations — each as a single Claude prompt with the exact JSON, PromQL, and provisioning YAML it writes.

Already know what skills are? Skip to the cookbook. First time? Read the explainer then come back. Need the install? It’s on the /skills/grafana-dashboards page.

Editorial illustration: a PromQL query on the left flowing into a fanned-out Grafana dashboard of time-series panels on the right, connected by a luminous teal arc, on a midnight navy background.
On this page · 21 sections
  1. What this skill does
  2. The cookbook
  3. Install + README
  4. Watch it built
  5. 01 · Service-health dashboard from a metrics list
  6. 02 · p95 latency panel with histogram_quantile
  7. 03 · Multi-service dropdowns with template variables
  8. 04 · Alert rule + notification policy
  9. 05 · Loki log panel beside the metrics
  10. 06 · Provisioned dashboard-as-code (JSON + YAML)
  11. 07 · Row + repeated panels per instance
  12. 08 · SLO burn-rate dashboard
  13. 09 · Import + re-theme a community dashboard
  14. 10 · Deploy annotations from a webhook
  15. Community signal
  16. The contrarian take
  17. Real dashboards as code
  18. Gotchas
  19. Pairs well with
  20. FAQ
  21. Sources

What this skill actually does

Sixty seconds of context before the cookbook — what the grafana-dashboards skill is, what Claude returns when you invoke it, and the one thing it does NOT do for you.

What this skill actually does

Create and manage production Grafana dashboards for real-time visualization of system and application metrics.

wshobson, the skill author · /skills/grafana-dashboards

What Claude returns

When triggered, Claude returns Grafana dashboard JSON you can import directly: a `panels` array (stat, timeseries, table, heatmap, logs), each with a `gridPos`, a `datasource` reference by UID, and PromQL or LogQL `targets`. It orders panels by the RED method (rate, errors, duration) or USE method, writes the `templating` block for query variables, and on request emits the file-based provisioning YAML (`apiVersion: 1`, a file provider, `updateIntervalSeconds`) so the dashboard loads as code on boot.

What it does NOT do

It does not run Grafana or expose your metrics — you still need a Grafana instance and a Prometheus/Loki datasource already scraping the series the panels query.

How you trigger it

Build a Grafana dashboard for the checkout service.Add a p95 latency panel with histogram_quantile.Make this dashboard provision-ready with the YAML.

Cost when idle

~100 tokens at idle (the skill name + description in the system prompt). The panel-type reference, PromQL patterns, and provisioning examples load only when the skill is triggered.

The cookbook

Each entry below is a dashboard you could import this week. They run roughly in order of depth — the early ones produce a single dashboard JSON, the middle ones lean on Grafana primitives (template variables, alert rules, Loki panels, provisioning), and the later ones compose the skill with SLO math and CI. Every entry pairs with one or two skills or MCP servers you already have on mcp.directory.

Install + README

If the skill isn’t on your machine yet, here’s the one-liner. The full install panel (Codex, Copilot, Antigravity variants) is on the skill page — the same UI’s embedded below.

One-line install · by wshobson

Open skill page

Install

mkdir -p .claude/skills/grafana-dashboards && curl -L -o skill.zip "https://mcp.directory/api/skills/download/83" && unzip -o skill.zip -d .claude/skills/grafana-dashboards && rm skill.zip

Installs to .claude/skills/grafana-dashboards

Watch it built

A walkthrough of generating Grafana dashboards from code and keeping them in version control. It uses Grafonnet rather than an LLM, but the target artefact is identical — dashboard JSON under Git — so it anchors what the cookbook below automates before you read the prompts.

01

Service-health dashboard from a metrics list

Hand the skill a list of metric names and a layout intent, get back a complete Grafana dashboard JSON: a stat row up top, time-series below, RED-method ordering (rate, errors, duration). Import it straight into Grafana.

ForBackend engineers standing up the first dashboard for a new service.

The prompt

Build a Grafana dashboard JSON for the `checkout` service. Metrics available: `http_requests_total`, `http_request_duration_seconds_bucket`, `http_requests_total{code=~"5.."}`. Order it RED-method: top row three stat panels (req/s, error %, p95 ms), second row two time-series (request rate by route, error rate). Datasource is a Prometheus datasource referenced by its UID, schemaVersion 39. Title 'Checkout — Service Health'. Save as `dashboards/checkout-health.json`.

What slides.md looks like

{
  "title": "Checkout — Service Health",
  "schemaVersion": 39,
  "panels": [
    {
      "type": "stat", "title": "Requests / sec",
      "gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
      "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
      "targets": [
        { "expr": "sum(rate(http_requests_total{service=\"checkout\"}[5m]))" }
      ]
    },
    {
      "type": "timeseries", "title": "Request rate by route",
      "gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
      "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
      "targets": [
        { "expr": "sum(rate(http_requests_total{service=\"checkout\"}[5m])) by (route)" }
      ]
    }
  ]
}

One-line tweak

Ask for the USE method instead (utilization, saturation, errors) and the same prompt rebuilds the dashboard around CPU, queue depth, and OOM counters for an infra view.

02

p95 latency panel with histogram_quantile

Get the one PromQL expression people always look up wrong — a p95 over a histogram, bucketed correctly by `le` — wrapped in a ready time-series panel with the right unit and a 500ms threshold line.

ForAnyone who has pasted a broken histogram_quantile into Grafana at 2am.

The prompt

Generate a Grafana time-series panel JSON that plots p95 request latency from `http_request_duration_seconds_bucket`, broken down by route. Use `histogram_quantile(0.95, ...)` summed by `le, route`. Set the field unit to seconds (`s`), add a red threshold step at 0.5, and title it 'p95 latency by route'. Prometheus datasource by UID.

What slides.md looks like

{
  "type": "timeseries",
  "title": "p95 latency by route",
  "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
  "fieldConfig": {
    "defaults": {
      "unit": "s",
      "thresholds": {
        "mode": "absolute",
        "steps": [
          { "value": null, "color": "green" },
          { "value": 0.5, "color": "red" }
        ]
      }
    }
  },
  "targets": [
    {
      "expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route))",
      "legendFormat": "{{route}}"
    }
  ]
}

One-line tweak

Swap 0.95 for 0.99 and duplicate the target — now the panel overlays p95 and p99 on the same axis without touching anything else.

03

Multi-service dropdowns with template variables

Turn a static dashboard into one that works for every service via query-based variables. The skill writes the `templating` block (a `$namespace` and a `$service` variable) and rewrites every panel query to filter on them.

ForPlatform teams who refuse to maintain one dashboard per service.

The prompt

Add templating to `dashboards/checkout-health.json`. Two query variables: `namespace` from `label_values(kube_pod_info, namespace)`, and `service` from `label_values(http_requests_total{namespace="$namespace"}, service)` with multi-value + 'All' enabled. Rewrite every panel target to filter `{namespace="$namespace", service=~"$service"}`. Keep the panel layout unchanged.

What slides.md looks like

"templating": {
  "list": [
    {
      "name": "namespace", "type": "query",
      "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
      "query": "label_values(kube_pod_info, namespace)",
      "refresh": 2, "sort": 1
    },
    {
      "name": "service", "type": "query",
      "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
      "query": "label_values(http_requests_total{namespace=\"$namespace\"}, service)",
      "includeAll": true, "multi": true, "refresh": 2
    }
  ]
}
// panel target becomes:
// sum(rate(http_requests_total{namespace="$namespace", service=~"$service"}[5m]))

One-line tweak

Add a `$quantile` custom variable with values 0.5,0.9,0.99 and pipe it into histogram_quantile so viewers pick the percentile from a dropdown.

04

Alert rule + notification policy

Generate a Grafana-managed alert rule (the unified alerting model, not the legacy dashboard alert) plus the notification policy that routes it. Threshold, `for` duration, and labels all wired so it reaches the right receiver.

ForOn-call engineers who want the alert defined next to the dashboard, in code.

The prompt

Write a Grafana alert rule (provisioning YAML, apiVersion 1) that fires when 5xx error rate on `checkout` exceeds 5% for 5 minutes. Use a reduce + threshold expression chain over `sum(rate(http_requests_total{service="checkout",code=~"5.."}[5m])) / sum(rate(http_requests_total{service="checkout"}[5m]))`. Label `severity: page`, `team: payments`. Then add a notification policy that routes `team=payments` to a `pagerduty-payments` receiver.

What slides.md looks like

apiVersion: 1
groups:
  - orgId: 1
    name: checkout-slo
    folder: Checkout
    interval: 1m
    rules:
      - title: Checkout 5xx error rate high
        condition: C
        data:
          - refId: A
            relativeTimeRange: { from: 600, to: 0 }
            datasourceUid: ${DS_PROM}
            model:
              expr: >
                sum(rate(http_requests_total{service="checkout",code=~"5.."}[5m]))
                / sum(rate(http_requests_total{service="checkout"}[5m]))
          - refId: C
            datasourceUid: __expr__
            model: { type: threshold, expression: A,
                     conditions: [ { evaluator: { type: gt, params: [0.05] } } ] }
        for: 5m
        labels: { severity: page, team: payments }

One-line tweak

Bump `for` to 15m and add a second rule at 2% with `severity: ticket` to get a warn-then-page ladder out of one metric.

05

Loki log panel beside the metrics

Drop a logs panel next to the latency graph so a spike and its stack traces sit on one screen. The skill writes a `logs` panel against a Loki datasource with a LogQL query filtered to the same service.

ForSREs tired of pivoting from Grafana to a separate logs tab during an incident.

The prompt

Add a Loki logs panel to the bottom row of the checkout dashboard. Loki datasource by UID. LogQL: `{namespace="$namespace", app="checkout"} |= "error" | json | line_format "{{.level}} {{.msg}}"`. Full width, 10 rows tall, newest-first, wrap off. Title 'Checkout errors (Loki)'.

What slides.md looks like

{
  "type": "logs",
  "title": "Checkout errors (Loki)",
  "gridPos": { "h": 10, "w": 24, "x": 0, "y": 20 },
  "datasource": { "type": "loki", "uid": "${DS_LOKI}" },
  "options": { "showTime": true, "sortOrder": "Descending", "wrapLogMessage": false },
  "targets": [
    {
      "expr": "{namespace=\"$namespace\", app=\"checkout\"} |= \"error\" | json | line_format \"{{.level}} {{.msg}}\"",
      "queryType": "range"
    }
  ]
}

One-line tweak

Change the panel `type` to `timeseries` and the LogQL to `sum(rate({app="checkout"} |= "error" [5m]))` to graph error-log volume instead of streaming lines.

06

Provisioned dashboard-as-code (JSON + YAML)

Get both halves of dashboards-as-code in one shot: the dashboard JSON and the file-based provisioning YAML that tells Grafana to load it on boot. No clicking 'Import' ever again.

ForAnyone putting Grafana under Git and a CI pipeline.

The prompt

Take `dashboards/checkout-health.json` and make it provision-ready. Write `provisioning/dashboards/dashboards.yml` (apiVersion 1) pointing a file provider at `/var/lib/grafana/dashboards`, `updateIntervalSeconds: 10`, `disableDeletion: false`, `allowUiUpdates: false` so UI edits can't silently fork the file. Strip the dashboard `id` to null and pin `uid: checkout-health` so the provisioned dashboard is stable across reloads.

What slides.md looks like

# provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
  - name: 'service-dashboards'
    orgId: 1
    folder: 'Services'
    type: file
    disableDeletion: false
    updateIntervalSeconds: 10
    allowUiUpdates: false
    options:
      path: /var/lib/grafana/dashboards
      foldersFromFilesStructure: true

# dashboards/checkout-health.json (head)
# { "id": null, "uid": "checkout-health", "title": "Checkout — Service Health", ... }

One-line tweak

Flip `allowUiUpdates` to true in a staging instance so people can prototype in the UI, then export the JSON back into Git once it's good.

07

Row + repeated panels per instance

One panel definition that fans out into a panel per pod, host, or instance via Grafana's `repeat`. The skill sets `repeat` on a row keyed to an `$instance` variable so the dashboard grows with your fleet automatically.

ForInfra teams whose instance count changes and who hate editing dashboards for it.

The prompt

Add an `instance` query variable (`label_values(node_cpu_seconds_total, instance)`, multi, includeAll) to a node dashboard. Create a row titled '$instance' with `repeat: instance` and `repeatDirection: h`, containing one time-series panel that graphs CPU busy %: `100 - (avg by (instance)(rate(node_cpu_seconds_total{mode="idle", instance="$instance"}[5m])) * 100)`. Grafana renders one row per selected instance.

What slides.md looks like

{
  "type": "row",
  "title": "$instance",
  "repeat": "instance",
  "repeatDirection": "h",
  "panels": [
    {
      "type": "timeseries", "title": "CPU busy %",
      "fieldConfig": { "defaults": { "unit": "percent", "max": 100 } },
      "datasource": { "type": "prometheus", "uid": "${DS_PROM}" },
      "targets": [
        { "expr": "100 - (avg by (instance)(rate(node_cpu_seconds_total{mode=\"idle\", instance=\"$instance\"}[5m])) * 100)" }
      ]
    }
  ]
}

One-line tweak

Move `repeat` from the row onto a single panel with `repeatDirection: v` to get a vertical stack of per-instance panels instead of full rows.

08

SLO burn-rate dashboard

A dashboard built around an error budget, not raw errors: fast-burn (1h) and slow-burn (6h) windows, the multi-window burn-rate math, and a remaining-budget stat. The shape Google's SRE workbook recommends.

ForTeams running a real SLO with a 99.9% target and an error budget.

The prompt

Build an SLO burn-rate dashboard for a 99.9% availability target on `checkout`. Define good = non-5xx requests. Panels: remaining error budget (stat, %), 1h burn rate, 6h burn rate, and a time-series of both burn rates with a threshold line at 1. Burn rate = (1 - (sum(rate(good[w])) / sum(rate(total[w])))) / (1 - 0.999). Annotate that >14.4 over 1h is a fast-burn page.

What slides.md looks like

// 1h burn rate target
(
  1 - (
    sum(rate(http_requests_total{service="checkout",code!~"5.."}[1h]))
    /
    sum(rate(http_requests_total{service="checkout"}[1h]))
  )
) / (1 - 0.999)

// remaining 30d error budget (stat panel, unit "percentunit")
1 - (
  (1 - (sum(increase(http_requests_total{service="checkout",code!~"5.."}[30d]))
        / sum(increase(http_requests_total{service="checkout"}[30d]))))
  / (1 - 0.999)
)

One-line tweak

Parametrise the target: add a `$slo` variable with 0.99,0.999,0.9995 and reference `(1 - $slo)` in the denominators so one dashboard serves several SLO tiers.

09

Import + re-theme a community dashboard

Take a public dashboard from grafana.com by its ID, then have the skill rewrite the datasource references and units to match your stack so it actually renders instead of showing 'datasource not found'.

ForEngineers who imported a Grafana.com dashboard and got empty panels.

The prompt

I imported Grafana.com dashboard 1860 (Node Exporter Full). Every panel shows 'datasource not found' because it references a datasource named 'Prometheus' and mine is a UID. Rewrite the JSON so every `datasource` field uses `{ "type": "prometheus", "uid": "$DS_PROM" }`, add a dashboard-level `__inputs` entry for `DS_PROM`, and convert any bytes panels to use the `bytes` unit. Leave the queries untouched.

What slides.md looks like

"__inputs": [
  {
    "name": "DS_PROM",
    "label": "Prometheus",
    "type": "datasource",
    "pluginId": "prometheus"
  }
],
// every panel + target datasource rewritten from:
//   "datasource": "Prometheus"
// to:
//   "datasource": { "type": "prometheus", "uid": "${DS_PROM}" }

One-line tweak

Ask it to also prune panels you don't use (e.g. the SCTP and NFS rows from 1860) so the imported dashboard isn't 200 panels of noise.

10

Deploy annotations from a webhook

Mark every deploy on the timeline so a latency spike lines up with the release that caused it. The skill writes a dashboard annotation query plus the curl that posts an annotation to Grafana's API from your CI pipeline.

ForAnyone who has asked 'did the 3pm deploy cause this?' and had no marker to check.

The prompt

Add a built-in annotation to the checkout dashboard that shows deploy markers tagged `deploy` and `checkout` as a vertical line. Then write the `curl` for our GitHub Actions deploy job to POST to Grafana's `/api/annotations` with the current epoch-ms `time`, tags `["deploy","checkout"]`, and text `Deployed ${GITHUB_SHA}`. Use a service-account token from `$GRAFANA_TOKEN`.

What slides.md looks like

// dashboard annotations block
"annotations": { "list": [
  {
    "name": "Deploys", "enable": true, "iconColor": "purple",
    "datasource": { "type": "grafana", "uid": "-- Grafana --" },
    "target": { "type": "tags", "tags": ["deploy", "checkout"], "limit": 100 }
  }
]}

# CI step
curl -s -X POST "$GRAFANA_URL/api/annotations" \
  -H "Authorization: Bearer $GRAFANA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"time\": $(date +%s%3N),
       \"tags\": [\"deploy\",\"checkout\"],
       \"text\": \"Deployed $GITHUB_SHA\"}"

One-line tweak

Add a `timeEnd` to the POST and Grafana draws a shaded region instead of a line — handy for marking a maintenance window rather than an instant.

Community signal

Three voices from people managing Grafana dashboards as code. The first quantifies why raw JSON authoring hurts, the second is the real-world workflow most teams land on, the third is the case for pulling alerts out of the dashboard blob.

Grafana's json configs have the same issue. I have a dashboard for one project. The json is over 13k lines long. Less than 5% of that is unique.

chris11 · Hacker News

On a Grafana Labs thread, a user quantifies how bloated and repetitive a raw Grafana dashboard JSON model gets — the exact problem generation solves.

Source
Used to wrangle the JSON with Chef+Jenkins, now wrangle the JSON with Terraform+Spacelift. Simple procedure document for building it out with the wysiwyg in a sandbox environment then smuggling out the JSON.

healydorf · Reddit

In an r/devops thread on managing Grafana across environments, the build-in-UI-then-extract-JSON workflow most teams settle into.

Source
Alerts are also integrated tightly in dashboards. Forces alerts to be saved/backedup/imported as single json blob. We want separate management of alerts so they can be defined as code and not in the dashboard blob of json!

wernerb · Hacker News

A practitioner explains why dashboard-embedded alerts pushed them toward managing alert rules as code — the move use case 4 makes.

Source

The contrarian take

Not everyone enjoys the Grafana JSON model. The bluntest first-day reaction on Hacker News, from TheLegace:

Today was the first day I ever used Grafana and what a terrible day its been. If there is going to be an editable JSON panel at least separate out the presentation and data layers.

TheLegace · Hacker News

From a Hacker News thread on Grafana — the editable JSON panel mixes presentation and data, which is exactly where hand-editing goes wrong.

Source

Fair, and it cuts to why this skill exists. Hand-editing the JSON model is miserable precisely because presentation (gridPos, units, thresholds) and data (the PromQL targets) live tangled in the same object. Letting Claude write it doesn’t fix the format — it just means you describe the panel in English and review the diff, instead of counting gridPos pixels by hand. The format stays ugly; your time stops being spent on it.

One comparison worth naming: the Grafana MCP server covers the other half. The skill in this cookbook authors dashboards; the MCP operates a running Grafana — importing the JSON, querying Prometheus and Loki, managing alerts. The trade-off is the usual skill-vs-MCP one: the skill is ~100 idle tokens, the MCP’s tool schemas load every turn. Use the skill to write, the MCP to push.

Real dashboards shipped as code

Concrete examples from public projects. Most don’t use the Claude skill — they’re here to show what production dashboards-as-code looks like (mostly Jsonnet and Terraform), so you have a target shape in mind when you write the prompt.

Gotchas (the four that bite)

Sourced from the Grafana dashboard docs and the provisioning docs.

Datasource names aren't portable — UIDs are

A dashboard that references a datasource by name ('Prometheus') breaks the moment another instance names it differently. Reference by UID and add a matching __inputs entry instead. Use case 9 is the fix for the 'datasource not found' empty-panel state.

Provisioned dashboards are read-only by design

With allowUiUpdates: false, the Save button is disabled and edits don't persist — which confuses people who don't know the dashboard is file-provisioned. That's the point: the file is the source of truth. Prototype in a sandbox with it set to true, then export back to Git.

histogram_quantile needs the le label

A p95 over a histogram must sum rate() by (le) before histogram_quantile, or the percentile is silently wrong. If your buckets don't carry a le label, the panel renders an empty or flat line. Use case 2 shows the correct shape; verify your *_bucket metric actually has le.

UI edits drift from the committed JSON

Edit a dashboard live, forget to export, and Git no longer matches production. Pin a stable uid so reloads overwrite cleanly, keep one-way flow (sandbox → export → commit), and don't treat the live UI as the source of truth for anything provisioned.

Pairs well with

Curated to match the cookbook’s actual integrations: the metrics-and-alerting skills (prometheus-skill, alertmanager-rules-config, loki-mode, observability-engineer) plus the MCP servers the live-import use cases (1, 3, 5, 9, 10) lean on.

Two posts that compose well with this cookbook: What are Claude Code skills? covers the underlying mechanism, and What is MCP? explains the protocol behind the Grafana and Prometheus MCP servers this cookbook pairs with.

Frequently asked questions

Is there a Grafana skill for Claude, and which one is this?

Yes. This cookbook covers the grafana-dashboards skill by wshobson, installable from the /skills/grafana-dashboards page. It is a Claude Code skill (also loadable in Codex, Copilot, and Antigravity) that writes Grafana dashboard JSON, PromQL panels, template variables, alert rules, and provisioning YAML from a natural-language prompt. There is also a separate grafana-dashboard-creator skill if you want a different prompt style for the same job.

Do I need to know PromQL before I use the Grafana dashboard skill?

No. The skill writes the PromQL for you, including the parts people get wrong — histogram_quantile bucketed by le for a p95, rate() windows, and label filters that respect your template variables. Reading what it produced is still worth it: use cases 2 and 8 above show the exact expressions so you can sanity-check the math against your own metric names.

How does the skill handle datasource UIDs so dashboards don't break on import?

It references datasources by a templated UID (for example `${DS_PROM}`) and adds a matching `__inputs` entry, rather than hardcoding a datasource name. That is why use case 9 fixes the classic 'datasource not found' error on a community dashboard: the imported JSON named a datasource 'Prometheus', but your instance identifies it by UID. Keeping the reference templated is what makes the JSON portable across instances.

Can the skill generate provisioned dashboards-as-code, not just JSON to click-import?

Yes — that is use case 6. It writes both the dashboard JSON (with a pinned `uid` and `id: null`) and the `provisioning/dashboards/dashboards.yml` file provider that loads it on boot. Set `allowUiUpdates: false` and Grafana treats the file as the source of truth so a UI edit can't silently fork it. Set it to true in staging when you want to prototype in the UI first.

Should I use a Grafana MCP server instead of the grafana skill?

They do different jobs. The grafana-dashboards skill authors the JSON, PromQL, and YAML; the Grafana MCP server connects to a running Grafana so an agent can push dashboards, query Prometheus and Loki, and manage alerts live. The common pairing is to let the skill write the dashboard, then the MCP server import it. The skill is ~100 idle tokens; an MCP loads its tool schemas every turn, so reach for it when an agent needs live access, not just authoring.

Can it build Loki log panels and SLO burn-rate dashboards, or only basic metrics?

Both. Use case 5 adds a Loki logs panel with a LogQL query beside the metrics, and use case 8 builds a multi-window SLO burn-rate dashboard with the error-budget math from Google's SRE workbook. The skill knows the RED and USE methods, so asking for 'a service-health dashboard' produces a sensible panel order rather than a random grid.

Will UI edits drift from my committed dashboard JSON?

That is the central risk of dashboards-as-code, and the community quotes above name it. The skill mitigates drift two ways: it pins a stable `uid` so reloads overwrite cleanly, and the provisioning YAML it writes can set `allowUiUpdates: false` to block UI edits from forking the file. The discipline that actually holds is one-way: prototype in a sandbox, then export the JSON back into Git — not edit live and hope.

Sources

Primary

Community

Critical and contrarian

Internal

Keep reading