rohitg00/agentmemory · warning
[viewer] API ${fetchOpts.method || 'GET'} ${path} returned $
Error message
[viewer] API ${fetchOpts.method || 'GET'} ${path} returned ${res.status} What it means
This is a console.warn emitted by the agentmemory viewer's central `api()` helper (src/viewer/index.html:1338-1372) whenever a REST call to the local agentmemory daemon resolves with a non-2xx HTTP status. It is not a thrown exception: the helper logs the method, path, and status, then resolves to `null` so callers can uniformly treat null as 'request failed'. A 401 additionally opens the viewer auth prompt. Because it's a warning, the browser console shows it but the page continues running in a degraded state.
Source
Thrown at src/viewer/index.html:1352
host.classList.remove('open');
host.innerHTML = '';
}
async function api(path, opts) {
try {
var url = REST + '/agentmemory/' + path;
var headers = Object.assign({ 'Cache-Control': 'no-cache' }, (opts && opts.headers) || {});
var viewerToken = getViewerToken();
if (viewerToken && !headers.Authorization && !headers.authorization) {
headers.Authorization = 'Bearer ' + viewerToken;
}
var fetchOpts = Object.assign({}, opts || {}, { headers: headers });
var readErrorBody = fetchOpts.readErrorBody;
delete fetchOpts.readErrorBody;
var res = await fetch(url, fetchOpts);
if (!res.ok) {
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;View on GitHub (pinned to e04ba88819)
Solutions
- Check the daemon is up: `curl http://localhost:49134/agentmemory/health` (or your AGENTMEMORY_URL) and restart it if unreachable.
- If the warn is 401, re-enter the viewer token in the auth prompt, or verify the Authorization header / getViewerToken() value matches the daemon's expected token.
- If 404, confirm the endpoint path exists in your installed version (README REST endpoint list) and hard-refresh to clear a stale cached index.html.
- If 502/503 via a reverse proxy, fix the proxy upstream target so it points at the daemon's actual port.
- In caller code, handle the resolved `null` (e.g. loadGraph's error/disabled state) instead of assuming data — the helper never rejects.
Example fix
// before: assuming data is always present
const graph = await apiGet('graph');
render(graph.nodes); // TypeError if request returned 401/500 -> null
// after: handle null as 'request failed'
const graph = await apiGet('graph');
if (!graph) {
showViewerErrorState('Failed to load graph — is the daemon running?');
return;
}
render(graph.nodes); Defensive patterns
Strategy: fallback
Validate before calling
// Probe before relying on the API
async function isApiHealthy(baseUrl) {
try {
const res = await fetch(baseUrl + '/agentmemory/health', {
signal: AbortSignal.timeout(3000),
});
return res.ok || res.status === 503; // 503 with JSON body is still valid (see #1019)
} catch {
return false;
}
} Type guard
function isApiResult<T>(result: T | null): result is T {
return result !== null && typeof result === 'object';
}
// usage
const data = await apiGet('graph');
if (isApiResult(data)) { render(data.nodes); } Try / catch
// The helper resolves null instead of rejecting, so guard the result;
// wrap the call only to also catch network-level fetch rejections.
try {
const data = await apiPost('memory_search', { query });
if (data === null) {
// non-2xx: check console warn status (401 -> re-auth, 5xx -> daemon issue)
showFallbackUi();
} else {
render(data);
}
} catch (err) {
// fetch itself rejected (daemon down, CORS, aborted)
console.warn('viewer request failed:', err);
showFallbackUi();
} Prevention
- Always null-check the return value of api()/apiGet()/apiPost() — non-2xx resolves to null by design.
- Use readErrorBody: true (as the health badge does) when a non-2xx response still carries a usable JSON body.
- Keep the viewer token in sync with the daemon's secret; a rotated AGENTMEMORY_SECRET invalidates stored tokens (401).
- Poll /agentmemory/health on an interval and show a daemon-down banner before other calls fail.
- Hard-refresh the viewer after upgrading agentmemory so stale cached HTML doesn't call renamed endpoints.
When it happens
Trigger: Any `api()`/`apiGet()`/`apiPost()` call where `fetch(REST + '/agentmemory/' + path)` returns `res.ok === false`: daemon not running (connection succeeds via proxy but 502/503), 401 when the viewer token is missing/expired/rotated (AGENTMEMORY_VIEWER_TOKEN mismatch), 404 when the endpoint path or tool count changed across versions, 503 from the health endpoint when status is 'critical', or 4xx/5xx from malformed POST bodies.
Common situations: Developer opens the viewer dashboard while the agentmemory daemon is down or restarted with a different port; the stored viewer token was invalidated after `AGENTMEMORY_SECRET` changed so every call returns 401 and the auth prompt reappears; a stale cached index.html calls an endpoint renamed in a newer version (404); the health badge intentionally logs 503 when system status is critical.
Related errors
- [viewer] API error on ${path}:
- 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/df6bc74d66700b80.
Report an issue: GitHub.