koala73/worldmonitor · error · Error
Search manager destroyed
Error message
Search manager destroyed
What it means
Thrown by WebMcpSearchController.search() when, after awaiting bindings.waitForIndexReady(), bindings.isDestroyed() is true. destroy() already ran on the controller (unsubscribers cleared, result cache emptied), so an in-flight or subsequent search refuses to proceed: its index, modal, and subscriptions may be gone.
Source
Thrown at src/app/webmcp-search-controller.ts:96
this.subscribeAfterInitial(this.bindings.subscribeEntitlement, invalidate),
this.bindings.subscribeRuntimeConfig(invalidate),
this.bindings.subscribeWidgetAccess(invalidate),
];
}
public destroy(): void {
for (const unsubscribe of this.unsubscribers) unsubscribe();
this.unsubscribers = [];
this.resultCache.clear();
}
public async search(
query: string,
scope: DashboardSearchScope,
limit: number,
): Promise<DashboardSearchResponse> {
await this.bindings.waitForIndexReady();
if (this.bindings.isDestroyed()) throw new Error('Search manager destroyed');
this.bindings.refreshIndex();
const modal = this.bindings.getModal();
if (!modal) throw new Error('Search index is not initialised');
let searchResult = modal.search(query, scope as SearchScope);
if (
searchResult.flightCallsign
&& searchResult.orderedMatches.length === 0
&& this.bindings.hasPremiumAccess()
) {
try {
await this.bindings.fetchLiveFlight(searchResult.flightCallsign);
if (this.bindings.isDestroyed()) throw new Error('Search manager destroyed');
this.bindings.refreshIndex();
searchResult = modal.search(query, scope as SearchScope);
} catch (error) {
if (this.bindings.isDestroyed()) throw error;
// Live enrichment is optional. A failed lookup is an empty result.View on GitHub (pinned to eeab0a219f)
Solutions
- Catch this error and treat it as terminal for the controller — do not retry search on a destroyed instance; re-create the controller with the app
- Add an AbortSignal to search() and destroy() so teardown cancels in-flight searches explicitly instead of relying on the post-await check
- Queue or reject new searches at the entry point when bindings.isDestroyed() is already true, before awaiting index readiness
Example fix
// before
const response = await controller.search(query, scope, limit); // unhandled throw after destroy
// after
if (controller.isDestroyed()) return { ok: false, status: 'denied', reason: 'search_manager_destroyed' };
try {
const response = await controller.search(query, scope, limit);
} catch (e) {
if (e instanceof Error && e.message === 'Search manager destroyed') return deniedResult();
throw e;
} Defensive patterns
Strategy: type-guard
Validate before calling
if (controller.isDestroyed()) { // check before entry AND after every await
return { ok: false, status: 'denied', reason: 'search_manager_destroyed' };
} Type guard
function isSearchManagerUsable(controller: { isDestroyed(): boolean }): boolean {
return !controller.isDestroyed();
} Try / catch
try { return await controller.search(q, scope, limit); } catch (e) { if (e instanceof Error && e.message === 'Search manager destroyed') return DESTROYED_RESULT; throw e; } Prevention
- Add an AbortSignal to search()/destroy() so teardown cancels in-flight work instead of surfacing as this error
- Reject new searches at the entry point once destroyed
- Re-create the controller together with the app, never independently
When it happens
Trigger: search() is invoked and awaits index readiness; during that await the app (and thus the controller) is destroyed — teardown of the WebMCP search feature racing the first (or any) search call. A search issued after destroy() hits the same check immediately.
Common situations: Tab unload or HMR teardown during an agent's search_dashboard call; tests that destroy the controller while a search promise is pending; sequential tool calls where the second arrives after cleanup began.
Related errors
- app_destroyed
- Dashboard is no longer available.
- app_destroyed
- Country brief panel is not initialised
- map_unavailable
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/6ba72a957ed931aa.
Report an issue: GitHub.