koala73/worldmonitor · error · SafeWebMcpError

switch_monitor input preflight did not run.

Error message

switch_monitor input preflight did not run.

What it means

switch_monitor runs validateSwitchMonitorInput twice: once in a preflight (used for early rejection/analytics) and once in execute. If the execute-time validation returns not-ok, the code assumes an impossible state — the preflight should have caught it — and throws this internal-invariant SafeWebMcpError instead of the proper validation message. Seeing it means the validation pipeline itself is inconsistent, not that your input was merely invalid.

Solutions

  1. Retry with a freshly constructed, simple args object like { monitor: '<known-id-from-context>' }.
  2. Do not mutate the args object after the call; pass a literal.
  3. Verify the monitor id against get_dashboard_context output before calling.
  4. If it reproduces with a clean literal, report a bug — this message indicates a broken internal invariant.

Example fix

// before
const args = { monitor: 'ops' };
await tools.call('switch_monitor', args);
args.monitor = 'finance'; // mutation between preflight and execute breaks the invariant
// after
await tools.call('switch_monitor', { monitor: 'finance' });
Defensive patterns

Strategy: validation

Validate before calling

async function canSwitchTo(tools, monitor) {
  if (typeof monitor !== 'string' || monitor.length === 0) return false;
  const ctx = await tools.call('get_dashboard_context', {});
  return Array.isArray(ctx.monitors) && ctx.monitors.some((m) => m.id === monitor);
}

Type guard

function isPreflightInvariantError(e) {
  return e instanceof Error && e.message === 'switch_monitor input preflight did not run.';
}

Try / catch

try {
  return await tools.call('switch_monitor', { monitor });
} catch (e) {
  if (isPreflightInvariantError(e)) {
    // internal invariant broke: retry once with a fresh literal, then report a bug
    return tools.call('switch_monitor', { monitor: String(monitor) });
  }
  throw e;
}

Prevention

When it happens

Trigger: A switch_monitor call whose args pass one validation path but fail the other — e.g. args object mutated between preflight and execute, or the preflight/execute validators diverging (a code bug), causing validateSwitchMonitorInput to return ok:false inside execute despite preflight succeeding.

Common situations: Middleware or proxies mutating the args object between preflight and execution; a version mismatch where the host runs a different preflight implementation than the tool; a WorldMonitor regression in validateSwitchMonitorInput.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/services/webmcp.ts:1980

          limit: {
            type: 'integer',
            description: `Page size from 1 to ${MAX_MAP_LAYER_PAGE_SIZE}.`,
            minimum: 1,
            maximum: MAX_MAP_LAYER_PAGE_SIZE,
            default: DEFAULT_MAP_LAYER_PAGE_SIZE,
          },
        },
        additionalProperties: false,
      },
      annotations: { readOnlyHint: true },
      execute: withBindings(WEBMCP_SPA_TOOL.listMapLayers, async (args, extra) => {
        const parsed = parseMapLayerCatalogArgs(args);
        if (!parsed.ok) return parsed;
        return listMapLayerCatalog(
          {
            ...await app.listMapLayerCatalog(extra),
            targetCancellationSupported: Boolean(extra?.signal),
          },
          parsed.query,
          { targetOutputChars: TARGET_OUTPUT_CHARS },
        );
      }, trackEvent),
    },
    {
      name: WEBMCP_SPA_TOOL.listDashboardPanels,
      title: 'List Dashboard Panels',
      description:
        'Page the canonical dashboard panel catalog for this tab, including disabled and unmounted panels. Optional variant, category, enabled, and available filters. Follow nextCursor until hasMore is false. Does not return panel data or enable panels. Gated panels include a stable unavailableReason.',
      inputSchema: {
        type: 'object',
        properties: {
          variant: {
            type: 'string',
            description: 'Monitor variant to list. Omit to include every canonical panel ID.',
            enum: [...SITE_VARIANTS],
          },

View on GitHub (pinned to e586b8b4b8)