Comprehensive toolkit for validating, optimizing, and understanding Prometheus Query Language (PromQL) queries. Use this skill when working with PromQL queries, prometheus queries, metrics queries, alerting rules, recording rules, or grafana dashboards to check syntax, detect anti-patterns, identify optimization opportunities, and interactively plan queries with users.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Use this skill when you need to:
promql-generator instead.assets/ or a section of references/.This skill uses a two-phase interactive workflow: Phase 1 (Steps 1-4) presents validation results and asks clarifying questions, then stops and waits for user response before proceeding to Phase 2 (Steps 5-7) for tailored recommendations.
When a user provides a PromQL query, follow this workflow:
Run the syntax validation script to check for basic correctness:
./scripts/validate_syntax.py "<query>"Run the best practices checker to detect anti-patterns and optimization opportunities:
./scripts/check_best_practices.py "<query>"Parse and explain what the query does in plain English:
Ask the user clarifying questions to verify the query matches their intent:
⏸️ STOP HERE AND WAIT FOR USER RESPONSE before proceeding to Steps 5-7.
After understanding the user's intent:
When relevant, mention known limitations:
_bytes suffix. Please confirm if this is correct.")Based on validation results:
Reference Examples: When suggesting corrections, cite relevant examples using this format:
As shown in `examples/bad_queries.promql` (lines 91-97):
❌ BAD: `avg(http_request_duration_seconds{quantile="0.95"})`
✅ GOOD: Use histogram_quantile() with histogram bucketsCitation sources:
assets/good_queries.promql - for well-formed patternsassets/optimization_examples.promql - for before/after comparisonsassets/bad_queries.promql - for showing what to avoidreferences/best_practices.md - for detailed explanationsreferences/anti_patterns.md - for anti-pattern deep divesCitation Format: file_path (lines X-Y) with the relevant code snippet quoted
Give the user control:
Claude: "I've validated your query. It's syntactically correct, but I notice it queries http_requests_total without any label filters. This could match thousands of time series. What specific service or endpoint are you trying to monitor?"
User: [provides intent]
Claude: "Great! Based on that, here's an optimized version: rate(http_requests_total{job="api-service", path="/users"}[5m]). This calculates the per-second rate of requests to the /users endpoint over the last 5 minutes. Does this match what you need?"
User: [confirms or asks for changes]
Claude: [provides refined query or alternatives]
WHY: A query can parse without errors but produce wildly wrong results. Syntax validation only confirms the query can be evaluated; semantic validation confirms it measures what you intend.
BAD: Stop after validate_syntax.py returns success.
GOOD: Complete all 7 workflow steps, including intent comparison (Step 5) and optimization review (Step 6).
WHY: A query missing label filters may work in development against a small data set but fan out to thousands of series in production, causing dashboard timeouts.
BAD: Dismiss the high-cardinality warning with "it works fine locally".
GOOD: Add job=, namespace=, or service= label filters before merging.
WHY: Using rate() on a gauge or avg() on a histogram quantile produces results that are syntactically valid but statistically wrong. Metric type must be confirmed before the query pattern is evaluated.
BAD: Validate and optimize purely by pattern-matching without confirming counter vs gauge.
GOOD: Step 4 explicitly asks "Is this a counter, gauge, histogram, or summary?" before recommending changes.
for clause check on alert expressionsWHY: An alert expression without a for clause fires on a single evaluation, causing alert storms from transient spikes. The validator must flag missing for on all alerting rules.
BAD: Accept expr: error_rate > 0.05 as complete for an alerting rule.
GOOD: Flag absence of for and suggest a minimum for: 2m to reduce false positives.
rate() to a gauge or leave a counter rawWHY: A counter only ever increases, so its raw value or sum says little, while rate() on a gauge reads normal drops as counter resets. Both give plausible but wrong numbers (see references/anti_patterns.md).
BAD: sum(http_requests_total) or rate(node_memory_MemAvailable_bytes[5m]).
GOOD: sum(rate(http_requests_total{job="api"}[5m])) for counters, and the gauge used directly or with avg_over_time().
WHY: The mean of per-instance quantiles is not the quantile of the combined population, so the latency figure is misleading. The best practices checker flags this.
BAD: avg(http_request_duration_seconds{quantile="0.95"})
GOOD: histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
rate() or increase() selector without a range vectorWHY: rate() and increase() need a range vector, so a bare instant selector is a syntax error. The syntax script catches it, but the intent behind the window still has to be confirmed with the user.
BAD: rate(http_requests_total)
GOOD: rate(http_requests_total{job="api"}[5m])
WHY: Matchers such as job=~".*" match every series and are slower than equality matchers. The cardinality check flags unbounded selectors for this reason.
BAD: http_requests_total{job=~".*"}
GOOD: http_requests_total{job="api-service"}
WHY: A subquery spanning many days re-evaluates the inner expression at every step, which is slow and can time out. Precompute it instead.
BAD: max_over_time(rate(http_requests_total[5m])[95d:1m])
GOOD: Record rate(http_requests_total[5m]) as a recording rule, then query max_over_time(<recorded_series>[7d]).
WHY: Metric type and cardinality are inferred from naming conventions, so non-standard names and recording-rule series can trigger false positives. Stating them as fact erodes trust in the review.
BAD: "Your query is wrong because the script says foo_bytes is a gauge."
GOOD: "The script inferred a gauge from the _bytes suffix. Please confirm the type before I recommend changes."
Metric types are inferred from naming conventions (e.g., _total, _bytes). Non-standard names may be misclassified: ask the user to confirm when uncertain.
The scripts flag metrics without label selectors, but recording rule metrics (e.g., job:http_requests:rate5m) and low-cardinality cases are legitimate without filters. Users can safely ignore the warning when they know their cardinality is manageable.
Scripts cannot verify whether metrics exist or whether label values are valid in any specific Prometheus deployment. For production use, test queries against an actual Prometheus instance.
The scripts detect common anti-patterns but cannot catch business logic errors, context-specific optimizations (e.g., based on scrape interval or retention), or custom function behavior from extensions.
Run both scripts from the skill directory and read the JSON on standard output:
./scripts/validate_syntax.py 'rate(http_requests_total{job="api"}[5m])'
./scripts/check_best_practices.py 'avg(http_request_duration_seconds{quantile="0.95"})'The skill uses two main Python scripts:
Both scripts output JSON for programmatic parsing and human-readable messages for display.
pantheon-org/tekhne
c8416e9
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.