neondatabase/neon · error · ApiError
param {param_name} specified more than once
Error message
param {param_name} specified more than once What it means
Thrown by get_query_param in http-utils when a query parameter appears more than once in the query string with DIFFERENT values. The reduce-based logic deliberately tolerates repeated occurrences that carry the same value -- a workaround for Grafana Alloy's pyroscope scraper duplicating the 'seconds' parameter (grafana/alloy#3026) -- but conflicting duplicates produce ApiError::BadRequest (HTTP 400).
Source
Thrown at libs/http-utils/src/request.rs:61
};
let values = url::form_urlencoded::parse(query.as_bytes())
.filter_map(|(k, v)| if k == param_name { Some(v) } else { None })
// we call .next() twice below. If it's None the first time, .fuse() ensures it's None afterwards
.fuse();
// Work around an issue with Alloy's pyroscope scrape where the "seconds"
// parameter is added several times. https://github.com/grafana/alloy/issues/3026
// TODO: revert after Alloy is fixed.
let value1 = values
.map(Ok)
.reduce(|acc, i| {
match acc {
Err(_) => acc,
// It's okay to have duplicates as along as they have the same value.
Ok(ref a) if a == &i.unwrap() => acc,
_ => Err(ApiError::BadRequest(anyhow!(
"param {param_name} specified more than once"
))),
}
})
.transpose()?;
// if values.next().is_some() {
// return Err(ApiError::BadRequest(anyhow!(
// "param {param_name} specified more than once"
// )));
// }
Ok(value1)
}
pub fn must_get_query_param<'a>(
request: &'a Request<Body>,
param_name: &str,
) -> Result<Cow<'a, str>, ApiError> {View on GitHub (pinned to 8f60b04da4)
Solutions
- Send the parameter exactly once, or make every occurrence identical (?a=1&a=1 is allowed)
- Log the full incoming request URI and fix the client/URL-builder component that appends the parameter a second time
- If a middleware/proxy injects the duplicate, rename your parameter or strip the injected copy at the gateway
- If you genuinely need multi-value semantics, switch the handler off get_query_param to a multi-value-aware parser
Example fix
# before GET /v1/metrics?seconds=60&seconds=120 -> 400 param seconds specified more than once # after (single value, or identical duplicates) GET /v1/metrics?seconds=60 GET /v1/metrics?seconds=60&seconds=60 -> accepted
Defensive patterns
Strategy: validation
Validate before calling
use hyper::{Body, Request};
use url::form_urlencoded;
/// Fails fast with a clear message when `param_name` appears twice with different values.
pub fn check_no_conflicting_query_param(
request: &Request<Body>,
param_name: &str,
) -> Result<(), String> {
let Some(query) = request.uri().query() else {
return Ok(());
};
let mut values = form_urlencoded::parse(query.as_bytes())
.filter(|(k, _)| k == param_name)
.map(|(_, v)| v.into_owned());
let Some(first) = values.next() else {
return Ok(());
};
if values.any(|v| v != first) {
return Err(format!(
"param {param_name} specified more than once with different values; first was {first:?}"
));
}
Ok(())
} Try / catch
match get_query_param(request, "seconds") {
Err(ApiError::BadRequest(e)) if e.to_string().contains("specified more than once") => {
// 400 to the client, but log the full URI to find who duplicates the param
tracing::warn!(uri = %request.uri(), "conflicting duplicate query param");
return Err(ApiError::BadRequest(e));
}
other => other?,
} Prevention
- In URL builders, use a map/set keyed by parameter name instead of pushing (key, value) pairs repeatedly
- Log the final outgoing URI in client wrappers so injected duplicates are visible immediately
- When integrating scrapers (Alloy/Grafana), pin versions without the duplicate-params bug or normalize the query at the gateway
- Write a handler test that sends the same param twice with equal values (must pass) and with different values (must 400)
When it happens
Trigger: Any request to a handler built with get_query_param / must_get_query_param / parse_query_param whose query string repeats a parameter with conflicting values, e.g. 'GET /v1/tenant_config?limit=10&limit=50'. Same-value duplicates like '?seconds=30&seconds=30' are accepted and do not raise.
Common situations: Scrapers or SDKs that append their own copy of a parameter the caller already set (the Alloy pyroscope case); hand-built URLs that string-concatenate extra filters; API gateways or sidecars that inject a duplicate parameter with a different value; clients that send both a default and an explicit value.
Related errors
- no {param_name} specified in query parameters
- cannot parse query param {param_name}: {e}
- Unexpected request body
- error downloading extension {:?}: {:?}
- Error parsing {remote_endpoint}, expected host:port, got {er
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/442a9f6c93c3b879.
Report an issue: GitHub.