Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Prometheus & Monitoring By James Joyner IV · · 9 min read

Prometheus Error Guide: 'parse error: unexpected' PromQL Syntax Errors

Fix PromQL 'parse error: unexpected character/identifier' and 'no arguments for aggregate expression' errors: unbalanced brackets, range selectors, and aggregation syntax.

  • #prometheus-monitoring
  • #troubleshooting
  • #errors
  • #promql

Exact Error Message

PromQL parse errors are returned by the query engine before any data is touched. They appear in the Graph/Explore UI as a red banner, and in the HTTP API as a 400 with status: "error" and errorType: "bad_data". The exact text varies by mistake:

1:15: parse error: unexpected character: '}'
parse error at char 24: unexpected "]" in aggregation, expected ")"
parse error: no arguments for aggregate expression provided
1:8: parse error: unexpected identifier "by" in label matching, expected one of "," or "}"

The leading line:column (e.g. 1:15) is a one-based pointer into your expression. The number after char in parse error at char N is a zero-based byte offset.

What the Error Means

PromQL has a formal grammar, and the parser rejects any expression that does not match it. Unlike a runtime error (division by an empty vector, a type mismatch), a parse error means the engine never even built a query plan — the string you sent is not valid PromQL.

Every parse error pins the exact offset where the grammar broke. The token the parser found is reported as unexpected ..., and frequently it also tells you what it expected (expected ")"). Read both halves: the fix is almost always at or just before the reported column. Because this is a pure syntax check, it is independent of whether the metric exists, whether any target is up, or what time range you pick.

Common Causes

1. Unbalanced brackets, parentheses, or braces

The single most common cause. A missing ), an extra }, or a [ without a matching ]:

sum(rate(http_requests_total[5m])    # missing closing paren
rate(http_requests_total[5m]})       # stray brace

2. Label matcher quoting and operators

Matcher values must be double-quoted strings, and only =, !=, =~, !~ are valid:

http_requests_total{job=api}         # value not quoted
http_requests_total{job=="api"}      # == is not a matcher operator
http_requests_total{job~="api.*"}    # regex op is =~ not ~=

3. Range selector [5m] used where an instant vector is required

A range vector (metric[5m]) cannot be graphed or returned directly by an instant query, nor passed to functions that want an instant vector:

http_requests_total[5m]              # range vector — instant query rejects it
sum(http_requests_total[5m])         # sum() wants an instant vector

4. Aggregation by / without clause placement

by (...) and without (...) attach to the aggregation operator, and the label list lives in parentheses:

sum http_requests_total by (job)     # missing parens around the arg
sum by job (http_requests_total)     # label list needs parentheses
sum(http_requests_total) by (job)    # by must hug the operator, before or after the arg

Both sum by (job) (expr) and sum(expr) by (job) are valid; mixing them up trips the parser.

5. Wrong function arity

Calling a function with too few or too many arguments. no arguments for aggregate expression provided is the classic — an aggregator with an empty argument list:

sum()                                # aggregate needs an instant vector
histogram_quantile(req_bucket)       # needs (φ, vector): two args
rate(http_requests_total)            # rate() needs a RANGE vector: rate(metric[5m])

6. Reserved words and the empty by () clause

Aggregation keywords (by, without, on, ignoring, group_left, bool, offset) are reserved. Using one as a bare metric or label name, or writing by () with an empty grouping where the parser expects labels, breaks parsing.

How to Reproduce the Error

Send a deliberately malformed expression to the instant query endpoint:

curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=sum(rate(http_requests_total[5m])' | jq .
{
  "status": "error",
  "errorType": "bad_data",
  "error": "1:5: parse error: unexpected end of input, expected \")\""
}

The same string in the Graph UI underlines the offending position. promtool reproduces it offline:

promtool query instant http://localhost:9090 'sum http_requests_total by (job)'
query error: ... parse error: unexpected identifier "http_requests_total"

Diagnostic Commands

Validate the raw expression against the parser, no data required:

curl -s 'http://localhost:9090/api/v1/query' \
  --data-urlencode 'query=http_requests_total{job=api}' \
  | jq -r '.error'
1:24: parse error: unexpected identifier "api" in label matching, expected string

Use promtool query instant to get the same parser feedback from the CLI, which is convenient in CI:

promtool query instant http://localhost:9090 'rate(http_requests_total)'

Bisect a long expression by stripping it back to the innermost selector and rebuilding outward — paste each layer into Explore until the red banner appears, which isolates the broken layer:

http_requests_total{job="api"}
rate(http_requests_total{job="api"}[5m])
sum by (instance) (rate(http_requests_total{job="api"}[5m]))

Step-by-Step Resolution

  1. Read the column pointer. 1:15 means character 15. Put your cursor there; the bad token is at or just before it.

  2. Balance every bracket. Count (/), [/], {/}. Most editors will match-highlight; an unmatched opener is your culprit.

    sum(rate(http_requests_total[5m]))
  3. Fix matcher quoting and operators. Quote values, use =~ for regex:

    http_requests_total{job="api", status=~"5.."}
  4. Give each function the right vector type. rate(), increase(), irate(), delta(), deriv() take a range vector; sum(), abs(), histogram_quantile() take an instant vector:

    sum(rate(http_requests_total[5m]))           # rate over range, then sum the instant result
    histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
  5. Place by/without correctly and never leave the label list out of parentheses:

    sum by (job) (rate(http_requests_total[5m]))
  6. Re-run the corrected query in Explore or with promtool query instant. A 200 with a resultType of vector/matrix/scalar confirms the syntax is valid (an empty result is a different problem — the syntax is fine).

Prevention and Best Practices

  • Author and test queries in the Explore/Graph UI or with promtool query instant before pasting them into alert rules, recording rules, or dashboards.
  • Lint rule files in CI with promtool check rules rules.yml, which catches parse errors at PR time rather than at reload.
  • Keep nesting shallow and build outward: get the bare selector working, add rate(), then aggregate. Validate each layer.
  • Standardize on one by/without style across your team so reviewers spot misplaced clauses instantly.
  • The free incident assistant can rewrite a broken PromQL expression into a valid one and explain the fix; browse more under Prometheus and monitoring.

Frequently Asked Questions

What does the number in parse error at char 24 mean? It is a zero-based byte offset into your query string pointing at the unexpected token. The 1:15 form is the same location expressed as one-based line:column. Jump there first.

Why do I get no arguments for aggregate expression provided? You called an aggregator (sum, avg, max, count, etc.) with an empty argument list — sum() or sum by (job) (). Aggregators require an instant-vector argument: sum(rate(http_requests_total[5m])).

My query works in Explore but fails in an alert rule. Why? YAML quoting. A PromQL expression embedded in expr: may need block scalar style (expr: |) or careful escaping so that braces and quotes survive YAML parsing. Run promtool check rules to see the post-YAML expression the parser receives.

Is rate(http_requests_total) a parse error or a type error? It is a parse-time complaint about arity/type: rate() requires a range vector, so you must write rate(http_requests_total[5m]). Without the [5m] the engine has no range to compute a rate over.

Can I validate PromQL without a running Prometheus? Yes. promtool query instant needs a server, but promtool check rules file.yml parses every expression in a rule file offline and reports parse errors with their positions.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.