koala73/worldmonitor · error · DashboardBindingError

app_destroyed

app_destroyed

Error message

Dashboard is no longer available.

What it means

DashboardBindingError with code 'app_destroyed', thrown by the search_dashboard WebMCP binding right after waitForDashboardReady(false) resolves: state.isDestroyed is true, so the app instance was torn down between the readiness await and this check. It marks the binding as terminally unavailable — retrying against the same App instance cannot succeed.

Source

Thrown at src/App.ts:1832

            getPanelConfig: (panelId) => getEffectivePanelConfig(panelId, SITE_VARIANT),
            isPanelAllowed: (panelId, config) => (
              isPanelEntitled(panelId, config, hasPremiumAccess(getAuthState()))
            ),
            hasPremiumAccess: () => hasPremiumAccess(getAuthState()),
            applyViewChange: (viewAction) => {
              if (viewAction.view) trackMapViewChange(viewAction.view);
            },
            applyLayerChange: (layer, enabled, source) => (
              this.eventHandlers.applyMapLayerChange(layer, enabled, source)
            ),
          },
          syncUrlStateNow: () => this.eventHandlers.syncUrlStateNow(),
        });
      },
      searchDashboard: async (query, scope, limit) => {
        await this.waitForDashboardReady(false);
        if (this.state.isDestroyed) {
          throw new DashboardBindingError('app_destroyed', 'Dashboard is no longer available.');
        }
        let manager: SearchManager;
        try {
          manager = await this.ensureSearchManager();
        } catch (error) {
          if (this.state.isDestroyed) {
            throw new DashboardBindingError('app_destroyed', 'Dashboard is no longer available.');
          }
          throw error;
        }
        if (this.state.isDestroyed) {
          throw new DashboardBindingError('app_destroyed', 'Dashboard is no longer available.');
        }
        return manager.searchDashboard(query, scope, limit);
      },
      openSearchResult: async (resultKey) => {
        // A capability can only exist after search_dashboard initialized the
        // manager. Deny fabricated first-use keys without loading the lazy

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Catch DashboardBindingError and branch on its code: for 'app_destroyed', stop retrying and re-establish the session against a fresh App instance
  2. In dev workflows, reconnect the MCP/agent client after HMR instead of reusing the old tool bindings
  3. Ensure destroy() runs only after in-flight tool calls drain, if teardown ordering is under your control

Example fix

// before
try { await app.searchDashboard(q, scope, limit); } catch (e) { retry(3); } // pointless: instance is dead

// after
try {
  await app.searchDashboard(q, scope, limit);
} catch (e) {
  if (e instanceof DashboardBindingError && e.code === 'app_destroyed') {
    await reconnectToFreshApp(); // terminal for this instance
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (app.state.isDestroyed) return { ok: false, status: 'denied', reason: 'app_destroyed' };

Type guard

function isAppAlive(app: { state: { isDestroyed: boolean } }): boolean {
  return !app.state.isDestroyed;
}

Try / catch

try { await app.searchDashboard(q, scope, limit); } catch (e) { if (e instanceof DashboardBindingError && e.code === 'app_destroyed') { session.end(); } else throw e; }

Prevention

When it happens

Trigger: An agent calls search_dashboard concurrently with app teardown: SPA route teardown, page unload, or Vite HMR replacing the app while the dashboard-ready promise was pending. The readiness await resolved only after destroy() had already flipped isDestroyed.

Common situations: Dev-mode hot module reload while an agent session is connected; end-to-end tests that destroy() the App between tool invocations; the user closing the tab or navigating away mid tool-call.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/600fddbce6658674. Report an issue: GitHub.