koala73/worldmonitor · error · SafeWebMcpError

Dashboard navigation result exceeded the safe output limit.

Error message

Dashboard navigation result exceeded the safe output limit.

What it means

boundDashboardNavigationResult composes a navigation envelope plus a context snapshot whose size is pre-budgeted (contextBudget = NAVIGATION_MAX_OUTPUT_CHARS - envelopeChars + 2), then does a final check against NAVIGATION_MAX_OUTPUT_CHARS. If the final serialized navigation result (e.g. from switch_monitor or focus_country) still exceeds the navigation output budget it throws this SafeWebMcpError. It is the last-resort guard for navigation payloads.

Solutions

  1. Retry the navigation — if a transient field inflated the envelope it may pass on a second call.
  2. Update WorldMonitor so tool bounder and dashboard payload shapes are from the same version.
  3. Reduce navigation input surface (e.g. target a monitor/country without huge metadata) to confirm the trigger.
  4. Report a bug with the tool name — the envelope should be structurally bounded, not able to exceed the budget.

Example fix

// before
await tools.call('switch_monitor', { monitor: someUnvalidatedInput });
// after
const monitors = await tools.call('get_dashboard_context', {});
const valid = monitors.monitors.some((m) => m.id === target);
if (valid) await tools.call('switch_monitor', { monitor: target });
Defensive patterns

Strategy: try-catch

Validate before calling

async function monitorExists(tools, monitor) {
  const ctx = await tools.call('get_dashboard_context', {});
  return Array.isArray(ctx.monitors) && ctx.monitors.some((m) => m.id === monitor);
}

Try / catch

try {
  return await tools.call('switch_monitor', { monitor });
} catch (e) {
  if (e.message.includes('navigation result exceeded')) {
    console.error('oversized navigation result for', monitor);
    // fall back to a context refresh instead of the full navigation payload
    return tools.call('get_dashboard_context', {});
  }
  throw e;
}

Prevention

When it happens

Trigger: A navigation tool call (switch_monitor, focus_country, applyDashboardAction-backed tools) where the envelope itself is so large that even after shrinking the context to zero the total exceeds NAVIGATION_MAX_OUTPUT_CHARS.

Common situations: A dashboard version adding large fields to the navigation envelope; monitors/countries with pathological metadata inflating envelope fields; version mismatch between tool bounder and app payload shape.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-01). Data as JSON: /api/errors/b01dd361c162354e. Report an issue: GitHub.

Appendix: source

Thrown at src/services/webmcp.ts:1611

        'list',
        'tab_not_found',
        'That dashboard tab cursor is no longer available.',
      ));
    }
  }

  const buildPage = (
    pageTabs: typeof tabs,
    nextCursor: string | undefined,
  ): DashboardTabListSnapshot => ({
    activeTabId,
    tabs: pageTabs,
    tabCount,
    tabsTruncated: nextCursor !== undefined || snapshot.tabsTruncated === true,
    canCreate,
    cap,
    ...(createBlockReason ? { createBlockReason } : {}),
    ...(nextCursor ? { nextCursor: boundedText(nextCursor, 64) } : {}),
  });

  const remaining = tabs.slice(startIndex);
  const page: typeof tabs = [];
  for (let index = 0; index < remaining.length; index += 1) {
    const tab = remaining[index];
    if (!tab) continue;
    const candidate = [...page, tab];
    const following = remaining[index + 1];
    if (JSON.stringify(buildPage(candidate, following?.id)).length > TARGET_OUTPUT_CHARS) {
      break;
    }
    page.push(tab);
  }
  if (page.length === 0 && remaining[0]) page.push(remaining[0]);

  const consumed = startIndex + page.length;
  const nextCursor = consumed < tabs.length ? tabs[consumed]?.id : undefined;

View on GitHub (pinned to e586b8b4b8)