rohitg00/agentmemory · warning
[viewer] API error on ${path}:
Error message
[viewer] API error on ${path}: What it means
The viewer's shared `api(path, ...)` fetch wrapper catches any network/HTTP failure, logs `[viewer] API error on <path>:` with the error, and returns null, which the UI renders as empty data rather than a crash. It fires when fetch itself rejects (daemon down, CORS, aborted request) or when the surrounding code throws before res.json() succeeds (e.g. non-JSON response from a proxy or an auth redirect).
Source
Thrown at src/viewer/index.html:1369
if (res.status === 401) showViewerAuthPrompt();
console.warn('[viewer] API ' + (fetchOpts.method || 'GET') + ' ' + path + ' returned ' + res.status);
// Non-2xx responses resolve to null so callers can keep treating
// null as "request failed" (e.g. loadGraph's disabled/error state).
// The health endpoint opts out via readErrorBody: it intentionally
// responds 503 with a valid JSON body when status is "critical"
// (see #1019), and the dashboard badge needs that body.
if (!readErrorBody) return null;
try {
return await res.json();
} catch (parseErr) {
console.debug('[viewer] API ' + path + ' non-2xx body was not JSON:', parseErr);
return null;
}
}
hideViewerAuthPrompt();
return await res.json();
} catch (err) {
console.warn('[viewer] API error on ' + path + ':', err);
return null;
}
}
async function apiGet(path) { return api(path); }
async function apiPost(path, body) {
return api(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || {})
});
}
async function apiDelete(path, body) {
return api(path, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || {})
});
}View on GitHub (pinned to e04ba88819)
Solutions
- Confirm the daemon is running and the viewer/REST port matches: curl the same path on the daemon (e.g. curl http://localhost:<port>/agentmemory/health).
- Check the browser console/network tab for the actual failing request — status code or CORS/redirect cause.
- Re-authenticate with the viewer token if API calls return 401 (the auth prompt appears again).
- Fix AGENTMEMORY_URL / proxy configuration so the path returns JSON, not an HTML error page.
- Restart the viewer page after restarting the daemon so it picks up the correct base URL.
Example fix
// before
const data = await apiGet('/agentmemory/sessions');
render(data.rows); // crashes on null
// after
const data = await apiGet('/agentmemory/sessions');
if (!data) { showEmptyState('Could not reach agentmemory daemon'); return; }
render(data.rows); Defensive patterns
Strategy: fallback
Validate before calling
async function daemonReachable(base) {
try { const r = await fetch(base + '/agentmemory/health', { signal: AbortSignal.timeout(3000) }); return r.ok; }
catch { return false; }
} Type guard
function isApiPayload(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
const data = await apiGet(path);
if (!isApiPayload(data)) showEmptyState('daemon unreachable or returned non-JSON'); Try / catch
// the viewer api() wrapper already try/catches; consume its null return defensively
const data = await api(path);
if (data === null) {
renderError('Could not reach the agentmemory daemon — is it running?');
return;
} Prevention
- Check daemon health (curl /agentmemory/health) before troubleshooting viewer UI emptiness.
- Keep the daemon and viewer on the same agentmemory version; restart the viewer tab after daemon restarts.
- Ensure proxies in front of the daemon return JSON (not HTML login/error pages) for /agentmemory/* paths.
- Enter a valid viewer auth token when prompted to avoid persistent 401s.
- Watch the browser console for CORS/mixed-content errors if accessing the viewer cross-origin.
When it happens
Trigger: Raised in src/viewer/index.html:1369 whenever apiGet/apiPost is called while the agentmemory daemon is unreachable, the viewer auth prompt was bypassed with an invalid token (401 handling falls through), a proxy intercepts the request and returns non-JSON, or the request is aborted.
Common situations: Daemon not running or restart while the viewer tab stays open; AGENTMEMORY_URL / viewer port misconfigured; a reverse proxy serving an HTML login page instead of JSON; viewer auth token wrong so every call fails; browser blocking mixed content or CORS.
Related errors
- [viewer] API ${fetchOpts.method || 'GET'} ${path} returned $
- POST ${url} failed: ${res.status} ${res.statusText}${suffix}
- ${init?.method || "GET"} ${path} -> ${res.status} ${res.stat
- Cohere embedding failed (${response.status}): ${err}
- Gemini embedding failed (${response.status}): ${err}
AI-assisted analysis of rohitg00/agentmemory@e04ba88819 (2026-08-30).
Data as JSON: /api/errors/0dcc223373183bc4.
Report an issue: GitHub.