louislam/uptime-kuma · error · Error

Screenshot delay must be less than ${maxDelayFromInterval}ms

Error message

Screenshot delay must be less than ${maxDelayFromInterval}ms (0.5 × interval)

What it means

Thrown by Monitor.validate() for type "real-browser" when screenshot_delay >= interval*1000*0.5. Even though it may fit under the page.goto bound, a delay of half the interval or more risks overlapping the next scheduled heartbeat and blocking the monitor loop.

Source

Thrown at server/model/monitor.js:1767

        if (this.type === "real-browser") {
            // screenshot_delay validation
            if (this.screenshot_delay !== undefined && this.screenshot_delay !== null) {
                const delay = Number(this.screenshot_delay);
                if (isNaN(delay) || delay < 0) {
                    throw new Error("Screenshot delay must be a non-negative number");
                }

                // Must not exceed 0.8 * timeout (page.goto timeout is interval * 1000 * 0.8)
                const maxDelayFromTimeout = this.interval * 1000 * 0.8;
                if (delay >= maxDelayFromTimeout) {
                    throw new Error(`Screenshot delay must be less than ${maxDelayFromTimeout}ms (0.8 × interval)`);
                }

                // Must not exceed 0.5 * interval to prevent blocking next check
                const maxDelayFromInterval = this.interval * 1000 * 0.5;
                if (delay >= maxDelayFromInterval) {
                    throw new Error(`Screenshot delay must be less than ${maxDelayFromInterval}ms (0.5 × interval)`);
                }
            }
        }

        if (this.type === "mongodb" && this.databaseQuery) {
            // Validate that databaseQuery is valid JSON
            try {
                JSON.parse(this.databaseQuery);
            } catch (error) {
                throw new Error(`Invalid JSON in database query: ${error.message}`);
            }
        }
    }

    /**
     * Gets monitor notification of multiple monitor
     * @param {Array} monitorIDs IDs of monitor to get
     * @returns {Promise<LooseObject<any>>} object

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Set screenshot_delay to below 0.5 * interval * 1000 ms, e.g. for interval=60s keep delay < 30000ms.
  2. Prefer the smaller of the two bounds (0.5 * interval) as your working ceiling.
  3. Raise interval if a longer delay is genuinely needed.

Example fix

// before
{ type: "real-browser", interval: 60, screenshot_delay: 31000 }
// after
{ type: "real-browser", interval: 60, screenshot_delay: 25000 }
Defensive patterns

Strategy: validation

Validate before calling

function delayFitsInterval(delayMs, intervalSec) {
  return Number(delayMs) < intervalSec * 1000 * 0.5;
}

Type guard

function isDelayUnderIntervalBound(delayMs, intervalSec) {
  const n = Number(delayMs);
  return Number.isFinite(n) && n < intervalSec * 1000 * 0.5;
}

Try / catch

try {
  await bean.validate();
} catch (e) {
  if (/0\.5 . interval/.test(e.message)) return badRequest("screenshot_delay must be < 0.5 * interval ms");
  throw e;
}

Prevention

When it happens

Trigger: Save a real-browser monitor whose screenshot_delay passes the 0.8*interval check but is still >= 0.5*interval, e.g. interval=60 and screenshot_delay=31000 (>= 30000ms but < 48000ms). This is the stricter of the two screenshot guards.

Common situations: User tunes delay to just under the 0.8 bound and trips this tighter bound instead. Inheriting a delay value from a longer-interval template. Forgetting that the two screenshot guards are independent.

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/59d6f68c952d7215. Report an issue: GitHub.