nexu-io/open-design · error · Error

daemon ${resp.status} on ${url}: ${body || resp.statusText}

Error message

daemon ${resp.status} on ${url}: ${body || resp.statusText}

What it means

Thrown by the generic getJson helper when a GET request returns a non-2xx status. The message includes the HTTP status, the URL, and either the response body (via safeText) or statusText. This is the catch-all transport error for read operations in the MCP layer (active context, project list, etc.).

Source

Thrown at apps/daemon/src/mcp.ts:3007

  const subs = list.filter((p) =>
    String(p.name || '').toLowerCase().includes(lower),
  );
  if (subs.length === 1) { const p = subs[0]!; return { id: p.id, name: p.name, source: 'substring' as const }; }
  if (subs.length > 1) {
    const opts = subs.map((p) => `${p.name} (${p.id})`).join(', ');
    throw new Error(
      `multiple projects match "${arg}": ${opts}. Pass the UUID instead.`,
    );
  }
  throw new Error(`no project matches "${arg}"`);
}

async function getJson<T>(url: string, headers?: Record<string, string>): Promise<T> {
  const resp = await fetch(url, headers ? { headers } : undefined);
  if (!resp.ok) {
    const body = await safeText(resp);
    throw new Error(`daemon ${resp.status} on ${url}: ${body || resp.statusText}`);
  }
  return (await resp.json()) as T;
}

async function getFile(
  baseUrl: string,
  project: string,
  relPath: string,
  active: ActiveContext | null,
  resolved?: ResolvedProject | null,
  offset = 0,
  limit = 2000,
  headers?: Record<string, string>,
) {
  const segments = String(relPath)
    .split('/')
    .filter((s) => s.length > 0)
    .map(encodeURIComponent);

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the embedded status code: 401/403 → fix auth headers; 404 → wrong URL or daemon version; 500 → check daemon logs; 429 → back off.
  2. Run `pnpm tools-dev status --json` and `pnpm tools-dev logs --json` to inspect daemon health and errors.
  3. Confirm the base URL and port match the running daemon.
  4. Update the daemon and MCP client to matching versions if an endpoint moved.
  5. For 5xx, restart the daemon via tools-dev and retry once.

Example fix

// before: 401 from missing auth header
getJson(`${baseUrl}/api/active`)
// after
getJson(`${baseUrl}/api/active`, { Authorization: `Bearer ${token}` })
Defensive patterns

Strategy: try-catch

Validate before calling

async function safeGetJson<T>(url: string, headers?: Record<string,string>): Promise<T> {
  const resp = await fetch(url, headers ? { headers } : undefined);
  if (!resp.ok) throw new Error(`daemon ${resp.status}`);
  return await resp.json() as T;
}

Try / catch

try {
  return await getJson(url, headers);
} catch (e) {
  const msg = String((e as Error).message);
  if (/daemon 401|403/.test(msg)) await refreshAuth();
  if (/daemon 5\d\d/.test(msg)) await waitForDaemon();
  throw e;
}

Prevention

When it happens

Trigger: Daemon returns 4xx/5xx (e.g. 401 auth, 404 route, 500 internal); proxy returns an error page; the URL hit a removed/renamed endpoint; rate limiting (429).

Common situations: Missing or expired auth headers; daemon version mismatch where an endpoint was removed; daemon crashed and a reverse proxy served 502; CORS/network appliance injecting an error.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/3b7154a90eb99b70. Report an issue: GitHub.