koala73/worldmonitor · error · DashboardBindingError
app_destroyed
app_destroyed
Error message
app_destroyed
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.
Solutions
- Catch DashboardBindingError and branch on its code: for 'app_destroyed', stop retrying and re-establish the session against a fresh App instance
- In dev workflows, reconnect the MCP/agent client after HMR instead of reusing the old tool bindings
- 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
- Treat 'app_destroyed' as terminal — never retry against the same instance
- In tests, await tool promises before destroy()
- Re-derive agent tool bindings after HMR instead of reusing them
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
- Dashboard is no longer available.
- Search manager destroyed
- app_destroyed
- Country brief panel is not initialised
- Dashboard is no longer available.
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-27).
Data as JSON: /api/errors/600fddbce6658674.
Report an issue: GitHub.
Appendix: source
Thrown at src/App.ts:2108
if (viewAction.view) trackMapViewChange(viewAction.view);
},
getMapLayerRuntimeAvailability: this.getMapLayerRuntimeAvailability,
applyLayerChange: (layer, enabled, source) => (
this.eventHandlers.applyMapLayerChange(layer, enabled, source)
),
requireMapModePersistence: true,
},
syncUrlStateNow: () => this.eventHandlers.syncUrlStateNow(),
});
},
selectPanelTab: async (panelId, tab, execution) => {
return selectWebMcpPanelTab(this.state.panels, panelId, tab, {
waitForUiReady: () => this.waitForDashboardReady(false, execution?.signal),
prepareTab: (_selectedPanelId, selectedTab, signal) => (
selectedTab === 'physical'
? this.dataLoader.loadPhysicalPremiumComparison(signal)
: undefined
),
signal: execution?.signal,
});
},
searchDashboard: async (query, scope, limit, execution) => {
await this.waitForDashboardReady(false, execution?.signal);
throwIfWebMcpAborted(execution?.signal);
if (this.state.isDestroyed) {
throw new DashboardBindingError('app_destroyed', 'Dashboard is no longer available.');
}
let manager: SearchManager;
try {
manager = await raceWebMcpAbort(
this.ensureSearchManager(),
execution?.signal,
);
throwIfWebMcpAborted(execution?.signal);
} catch (error) {
if (this.state.isDestroyed) {View on GitHub (pinned to e586b8b4b8)