neondatabase/neon · warning · ApiError
duration must be 1-60 secs
Error message
duration must be 1-60 secs
What it means
Returned as HTTP 400 BadRequest by profile_cpu_handler in neon's http-utils when the seconds query parameter of /profile/cpu is present but outside the accepted 1..=60 range (parse failures produce a different 'cannot parse query param' error). The bounds cap how long the profiler may block the debugging endpoint; the default is 5 seconds.
Source
Thrown at libs/http-utils/src/endpoint.rs:384
/// Generates CPU profiles.
pub async fn profile_cpu_handler(req: Request<Body>) -> Result<Response<Body>, ApiError> {
enum Format {
Pprof,
Svg,
}
// Parameters.
let format = match get_query_param(&req, "format")?.as_deref() {
None => Format::Pprof,
Some("pprof") => Format::Pprof,
Some("svg") => Format::Svg,
Some(format) => return Err(ApiError::BadRequest(anyhow!("invalid format {format}"))),
};
let seconds = match parse_query_param(&req, "seconds")? {
None => 5,
Some(seconds @ 1..=60) => seconds,
Some(_) => return Err(ApiError::BadRequest(anyhow!("duration must be 1-60 secs"))),
};
let frequency_hz = match parse_query_param(&req, "frequency")? {
None => 99,
Some(1001..) => return Err(ApiError::BadRequest(anyhow!("frequency must be <=1000 Hz"))),
Some(frequency) => frequency,
};
let force: bool = parse_query_param(&req, "force")?.unwrap_or_default();
// Take the profile.
static PROFILE_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
static PROFILE_CANCEL: Lazy<Notify> = Lazy::new(Notify::new);
let report = {
// Only allow one profiler at a time. If force is true, cancel a running profile (e.g. a
// Grafana continuous profile). We use a try_lock() loop when cancelling instead of waiting
// for a lock(), to avoid races where the notify isn't currently awaited.
let _lock = loop {
match PROFILE_LOCK.try_lock() {View on GitHub (pinned to 8f60b04da4)
Solutions
- Set seconds to a value between 1 and 60 inclusive, e.g. ?seconds=30
- For longer coverage, take consecutive 60s profiles and merge them offline rather than one long request
- Check scrape config (pyroscope/alloy scrape interval) and clamp it to <=60s for this endpoint
Example fix
# before curl 'http://localhost:9898/profile/cpu?seconds=300' # 400 duration must be 1-60 secs # after curl 'http://localhost:9898/profile/cpu?seconds=60'
Defensive patterns
Strategy: validation
Validate before calling
const seconds = Math.min(Math.max(Number(process.env.PROFILE_SECONDS ?? 5), 1), 60);
await fetch(`/profile/cpu?seconds=${seconds}`); Type guard
function isValidProfileSeconds(s) { const n = Number(s); return Number.isInteger(n) && n >= 1 && n <= 60; } Prevention
- Clamp requested durations to 1-60 before calling the endpoint
- For longer coverage, run repeated 60s profiles and merge offline
- Keep pyroscope/alloy scrape intervals for this endpoint at or below 60s
When it happens
Trigger: GET /profile/cpu?seconds=0, ?seconds=61, or ?seconds=120 — any integer below 1 or above 60 returns this 400.
Common situations: Grafana continuous-profiling scrape configured with a 30s+ interval that drifts over 60; someone requesting a long profile to capture a rare event; default 15m/interval arithmetic producing out-of-range seconds.
Related errors
- invalid format {format}
- frequency must be <=1000 Hz
- heap profiling not enabled
- Cannot manage failpoints because neon was compiled without f
- Failed to configure failpoints: {err_msg}
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/91898b4d71779599.
Report an issue: GitHub.