promql-fmt
Static analyzer and auto-formatter for PromQL expr:/query: fields inside
Prometheus-style rule YAML files. Hermetic — it only reads the files you
point it at.
When to use this skill
- A user is authoring or editing a Prometheus/Thanos/Cortex/Mimir alert or recording rule and wants it formatted consistently.
- You're reviewing a diff to rule YAML and want to catch style or best-practice violations before commit.
- You're wiring a CI job that should fail a PR touching rule files with inconsistent formatting.
Setup
go build -o bin/promql-fmt ./cmd/promql-fmt
# or: go install github.com/conallob/o11y-analysis-tools/cmd/promql-fmt@latest
Usage
promql-fmt [options] <file|directory>...
Only .yml/.yaml files are scanned; directories are walked recursively.
There is no stdin mode (unlike label-check) — always pass a file or
directory path.
| Flag | Default | Effect |
|---|---|---|
--check |
true |
Report issues, exit 1 if any found. Do not modify files. |
--fix / --fmt |
false |
Rewrite files in place with the formatted output. --fmt is an alias for --fix. Passing either disables --check automatically. |
--verbose |
false |
Print per-file "Fixing …" lines while fixing. |
--disable-line-length |
false |
Suppress the "should use multiline formatting" nudge for long lines — useful for recording rules with unavoidably long metric names. |
--prometheus-url |
"" |
Optional. If set, additionally queries that Prometheus for timeseries continuity — i.e., flags expressions that reference metrics with no matching series. This is the one non-hermetic opt-in check; omit the flag to stay fully offline. |
Typical invocations
# CI gate / pre-commit check (default mode)
promql-fmt --check ./prometheus/
# Auto-fix in place, then re-check
promql-fmt --fix ./prometheus/
# Verbose fix, skip line-length nudges for long recording-rule names
promql-fmt --fix --verbose --disable-line-length ./prometheus/recording-rules.yml
What it actually checks/fixes
- Multiline layout: rewrites long/complex
expr:blocks into an indented multiline YAML block scalar (expr: |), removing a redundantby (...)clause from the left operand of a binary expression when both operands share the same aggregation labels, and inserting an expliciton (...)vector-matching clause. - Aggregation clause consistency: detects whether a file predominantly
uses postfix (
sum(metric) by (label)) or prefix (sum by (label) (metric)) style and flags expressions that break with the file's dominant style. - Naming conventions: snake_case metric names, application-name
prefixes, base units (warns on
_milliseconds/_minutes/etc., wants_seconds),_totalsuffix on counters,_ratioinstead of raw percentages,level:metric:operationsnaming for recording rules. - Instrumentation patterns:
rate()/irate()applied to something that doesn't look like a counter, division without zero-protection, utilization ratios where the denominator metric name doesn't containtotal,up{...}used without ajobselector. - Alert hysteresis sanity: warns when an alert's expression is
duration-sensitive but the rule has no
for:at all (a distinct, static check from the separatealert-hysteresistool, which instead tunes an existingfor:value against real firing history).
Reading the output
Check mode groups issues by file:
./alerts/api-alerts.yml:
- Metric 'httpRequestCount' should use snake_case, not camelCase
- Expression should use multiline formatting: sum(rate(http_requests_total...
Found formatting issues in 1/12 files
Run with --fix to automatically format
Exit code is 1 whenever any file has issues in check mode (or a write
error occurs in fix mode), 0 otherwise. Every issue is plain English —
there's no machine-readable JSON mode, so an agent should parse them as
free text and either explain them to the user or run --fix and re-check.
Agent workflow
- Run
promql-fmt --check <path>first. If exit code is 0, nothing to do. - If issues are found and they're purely formatting (multiline/aggregation
style), prefer
promql-fmt --fix <path>over manual edits, then re-run--checkto confirm. - If issues are best-practice warnings (naming, instrumentation), surface them to the user — these often require a human judgment call (e.g. renaming a metric is a breaking change for dashboards/alerts elsewhere).
- In CI, use
--checkonly; never run--fixunattended in a pipeline that auto-commits, since some rewrites (removing a "redundant" aggregation clause) change semantics if the assumption about matching labels doesn't actually hold — treat--fixoutput as something a human reviews before merge, same as any auto-formatter.