thanos-query — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited thanos-query (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.
Query Thanos HTTP API directly via curl. Thanos implements the full Prometheus HTTP v1 API plus Thanos-specific endpoints (/api/v1/stores, dedup/partial_response params). Covers instant/range queries, label/series discovery, stores topology, alerts, and rules across multiple Prometheus backends.
# $THANOS_METRICS_URL — base URL of Thanos Querier or Query Frontend HTTP API
# Required. Set in ~/.claude/settings.json under `env`.
# Example: http://thanos-query.example.internal:10902
# https://thanos.example.com
#
# $THANOS_AUTH_HEADER — optional full HTTP header line, if your deployment fronts
# Thanos with a reverse proxy that requires auth (basic/bearer).
# Example: Authorization: Bearer <token>
# Authorization: Basic <base64(user:pass)>
# Leave empty/unset when Thanos is reached directly (Thanos has no built-in auth).All curl commands use conditional auth — works for both reverse-proxied and direct deployments:
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/query?query=up" | jq .When THANOS_AUTH_HEADER is empty, the -H flag is omitted automatically.
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=up' \
"$THANOS_METRICS_URL/api/v1/query" | jq .
# Query at a specific time
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=up' \
--data-urlencode 'time=2026-03-07T09:00:00Z' \
"$THANOS_METRICS_URL/api/v1/query" | jq .Params: query (required), time (RFC3339 or unix seconds), timeout, dedup, partial_response, max_source_resolution.
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=rate(http_requests_total[5m])' \
--data-urlencode 'start=2026-03-07T00:00:00Z' \
--data-urlencode 'end=2026-03-07T12:00:00Z' \
--data-urlencode 'step=5m' \
"$THANOS_METRICS_URL/api/v1/query_range" | jq .Params: query, start, end, step (required); plus timeout, dedup, partial_response, max_source_resolution.
# All label names
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/labels" | jq '.data[]'
# Values of one label
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/label/region/values" | jq '.data[]'
# Find series matching a selector
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'match[]={job="api-server"}' \
"$THANOS_METRICS_URL/api/v1/series?limit=20" | jq '.data'# List all backing stores (sidecars, store-gateway, ruler, receive)
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/stores" | jq '.data | to_entries[] | {kind: .key, count: (.value | length)}'
# Detail: which stores per region, last-error state
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/stores" \
| jq '.data | to_entries[] | .key as $k | .value[] | {kind:$k, name, lastError, labelSets}'curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/alerts" | jq '.data.alerts[]'
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/rules" | jq '.data.groups[]'Thanos exposes each backend Prometheus's external_labels on every metric. Common label conventions:
region or thanos_region — the geographic / logical regioncluster — K8s cluster nameenvironment — env tier (dev/staging/prod)replica — HA Prometheus replica (deduped by querier)Use these to scope queries:
# Filter by region label
--data-urlencode 'query=up{region="us-west"}'
# Group by region for cross-region comparison
--data-urlencode 'query=count(up) by (region)'
# P95 latency per region per HTTP verb
--data-urlencode 'query=histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, region, verb))'Inspect which label names your deployment actually uses via /api/v1/labels first — the conventions above are common but not universal.
These extend the standard Prometheus query API:
| Param | Default | Use |
|---|---|---|
dedup | true | Dedup HA Prometheus pair series via replica label |
partial_response | depends on flag | Return what's available even if some store is down (useful for cross-region availability) |
max_source_resolution | 0s | Use downsampled blocks: 0s raw, 5m, 1h, or auto |
replicaLabels | from CLI | Override the replica label for this query |
storeMatch[] | none | Restrict to specific stores: {__address__=~"prom-foo.*"} |
engine | thanos | PromQL engine: prometheus or thanos |
Example — long range query against downsampled data:
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=avg_over_time(up[30d])' \
--data-urlencode 'max_source_resolution=1h' \
"$THANOS_METRICS_URL/api/v1/query" | jq .# Service health across all regions
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=count(up == 0) by (region, job)' \
"$THANOS_METRICS_URL/api/v1/query" | jq '.data.result'
# Error rate across regions (assumes standard label `code`)
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=sum(rate(http_requests_total{code=~"5.."}[5m])) by (region, job)' \
"$THANOS_METRICS_URL/api/v1/query" | jq '.data.result'
# P99 latency (histogram) — last 5 minutes per region
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, region))' \
"$THANOS_METRICS_URL/api/v1/query" | jq '.data.result'When an instant query or rate() query returns [], do NOT iteratively widen the time window in small steps (5m → 10m → 30m → 1h). That wastes time and obscures the real cause. Follow this checklist instead:
Empty result triage:
- [ ] Step 1: Check stores topology — is there a sidecar for that region?
- [ ] Step 2: Verify the metric name exists at all
- [ ] Step 3: Verify the label value (label names vary across deployments)
- [ ] Step 4: Run a wide range query (6h+) to find the last data point
- [ ] Step 5: Only narrow down once you have an upper bound on the gapBefore assuming the query is wrong, check what backs each region:
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
"$THANOS_METRICS_URL/api/v1/stores" \
| jq '.data | to_entries[] | .key as $k | .value[] | {kind:$k, name, lastError, labelSets}'Interpret the result:
| What you see for that region | What it means |
|---|---|
Has a sidecar entry, lastError: null | Real-time data available — empty result is a query problem |
Has a sidecar entry, lastError populated | Sidecar is broken — that's your answer |
Only store (store-gateway), no sidecar | Historical data only. Expect ~2h upload delay. rate() over last 5m will almost always be empty. |
| Neither sidecar nor store for that region | Region not wired into this Thanos — query the right Querier |
This single check resolves most "empty result" mysteries. Do it before anything else.
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'match[]={__name__=~".*duration.*bucket"}' \
"$THANOS_METRICS_URL/api/v1/series?limit=5" \
| jq '[.data[] | .__name__] | unique'If the metric name is wrong (e.g. http_request_duration_seconds_bucket vs api_request_duration_milliseconds_bucket), nothing else matters. Confirm the actual name first.
Don't assume environment="stg" is the right scope. Inspect actual labels on the metric:
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'match[]={__name__="<metric_name>"}' \
"$THANOS_METRICS_URL/api/v1/series?limit=1" | jq '.data[0]'Look for region, thanos_region, cluster, environment — different deployments scope differently.
Once topology and naming are confirmed, find the last data point with one wide query, not many narrow ones:
# 6 hours is the default — go wider if needed. NEVER start with 30 minutes.
NOW=$(date -u +%s)
curl -s ${THANOS_AUTH_HEADER:+-H} ${THANOS_AUTH_HEADER:+"$THANOS_AUTH_HEADER"} \
--data-urlencode 'query=count(<metric>{<labels>})' \
--data-urlencode "start=$(( NOW - 21600 ))" \
--data-urlencode "end=$NOW" \
--data-urlencode 'step=1m' \
"$THANOS_METRICS_URL/api/v1/query_range" \
| jq '.data.result[0].values | .[-5:] | map({time: (.[0] | todate), count: .[1]})'If still empty, double the window (12h, 24h, 3d). Cheap operation — go wide and bisect, don't crawl forward.
Only after you know roughly when data stopped, query the surrounding window with step=1m to pinpoint the exact cutoff.
rate(...[5m]) with no data — rate() needs ≥2 samples in the window; if scrape stopped, no time window will help.[5m] → [10m] → [30m] hoping data appears./api/v1/query (instant) after /api/v1/query_range showed data — staleness markers may hide instant results even when range queries succeed./api/v1/stores.application/x-www-form-urlencoded; use --data-urlencode for queries with special chars.match[] requires the [] suffix./api/v1/label/{name}/values./query, /query_range, /labels, /series, /alerts, /rules. /stores is Thanos-specific.THANOS_AUTH_HEADER accordingly.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.