#25268·matomo

Real-time map and Live widget stop refreshing permanently after a single failed request

Author: manolininvest-glitchCreated Sep 10, 2026Updated Sep 17, 2026
LabelsPotential Bugtriaged

What happened?

Both real-time surfaces re-arm their polling timer only inside the success callback, and
neither registers an error handler on the AJAX call. As a result a single failed request, for
any reason, silently kills the refresh loop for good.

The UI stays fully responsive, only the data freezes. Nothing is logged and nothing is shown
to the user. The only way to restart it is to recreate the widget, meaning navigate away and
back, or reload the page.

Affected code, verified in 5.13.0 and still present in 6.x-dev:

plugins/UserCountryMap/javascripts/realtime-map.js, in refreshVisits():

    ajax(_reportParams(firstRun)).done(gotNewReport);

The next setTimeout lives inside gotNewReport. The strings ".fail(", "error:" and "complete:"
do not appear anywhere in that file.

plugins/Live/javascripts/live.js, in _update():

    ajaxRequest.setCallback(function (r) {
        ...
        if (that.isStarted) {
            window.clearTimeout(that.updateInterval);
            that.updateInterval = window.setTimeout(function() { that._update(); }, that.currentInterval);
        }
    });
    ajaxRequest.send();

ajaxHelper does expose setErrorCallback(), it is simply not used here.

Real-world trigger: any transient failure does it, a network blip, a 502 from a reverse proxy,
a response that fails to parse as JSON, a laptop waking up. The case that made this
reproducible for us is a macOS Dock web app ("Add to Dock" in Safari). The system suspends the
window as soon as it goes to the background, which aborts whatever request is in flight. With
one request every 5 seconds the race is hit within days. The same Matomo in a normal, visible
Safari tab almost never freezes, which is what made the bug look random for a long time.

What should happen?

A failed request should not end the polling loop. The timer should be re-armed on failure the
same way it is on success, so that the report resumes on its own once requests succeed again.

Today a user watching the real-time map or the Live widget can be looking at data frozen
minutes or hours earlier, with no indication that it is stale.

How can this be reproduced?

PPure client-side, no server or network manipulation needed, so it reproduces on any instance including the demo. Steps below were run on a self-hosted 5.13.0.

Open Visitors > Real-time Map (or Visitors > Real-time for the Live widget), then in the browser console.

1. Instrument, one line per poll:

jQuery(document).ajaxSend(function (e, x, o) {
    console.log('REQ', new Date().toLocaleTimeString(), (o.url + '').slice(-60));
});

2. Wait for two or three REQ lines, then make the polled request fail for real, by rewriting its URL to a path that returns 403 or 404:

window.__killAjax = true;
jQuery.ajaxPrefilter(function (o) {
    if (window.__killAjax && (o.url + '').indexOf('getLastVisits') > -1) {
        o.url = '/plugins/__kill__' + Date.now();
    }
});

3. Wait about 60 seconds, then restore:

window.__killAjax = false;

Expected: REQ lines keep appearing for the whole failure window, pointing at the bogus URL, and go back to the normal URL once restored.

Actual: exactly one failing request, then silence. The report stays frozen indefinitely while the rest of the UI keeps responding. Navigating to another menu and back restarts it.

Two notes for whoever tries this.

Do not simulate the failure with jQuery.ajaxSetup({ beforeSend: ... }) returning false. That path is not equivalent and it silently gives a wrong result on the live.js half: jQuery evaluates beforeSend before the line jqXHR.fail( s.error ) that installs the option callbacks, so an abort from beforeSend never reaches an error: option. It does reach a .fail() chained by the caller. Since realtime-map.js chains .fail() while ajaxHelper passes error: as an option, the same simulation exercises one file and not the other. Verified empirically on jQuery 3.7.1.

Keep the window visible throughout, otherwise the Visibility.hidden() branch of the map skips the request instead of issuing it and the test proves nothing.

Matomo version

5.13.0 (code also checked on 6.x-dev, unchanged)

PHP version

8.4.24

Server operating system

Debian GNU/Linux 13 (trixie), official matomo:5-apache Docker image

What browsers are you seeing the problem on?

Safari

Computer operating system

macOS 26.6.2

Relevant log output

Validations