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

  1. Check the daemon is up: `curl http://localhost:49134/agentmemory/health` (or your AGENTMEMORY_URL) and restart it if unreachable.
  2. 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.
  3. 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.
  4. If 502/503 via a reverse proxy, fix the proxy upstream target so it points at the daemon's actual port.
  5. 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

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


AI-assisted analysis of rohitg00/agentmemory@e04ba88819 (2026-08-30). Data as JSON: /api/errors/df6bc74d66700b80. Report an issue: GitHub.