koala73/worldmonitor · error · Error
Search unavailable
Error message
Search unavailable
What it means
loadIndex lazily fetches /sources/search-index.json to build the sources search index. If the response is not ok (404, 500, etc.) it throws 'Search unavailable'. The promise is reset to null in the catch so a later call retries the fetch. The thrown error propagates to callers awaiting the index (e.g. apply()).
Solutions
- Verify /sources/search-index.json is reachable in the browser (network tab) and check its HTTP status.
- Regenerate/deploy the search index as part of the build (ensure the crawlable-sources-page/index build step ran).
- Since loadIndex resets indexPromise on failure, trigger a retry by re-invoking the search action after fixing the file.
- If the path is wrong relative to the site base URL, fix the fetch path or server rewrite.
Defensive patterns
Strategy: retry
Validate before calling
const res = await fetch('/sources/search-index.json', { method: 'HEAD' });
if (!res.ok) {
console.warn('search index missing; disabling search UI');
disableSearch();
} Try / catch
try {
const index = await loadIndex();
} catch (e) {
if (e.message === 'Search unavailable') {
showSearchFallback('Search is temporarily unavailable.');
return; // loadIndex resets its promise, so a later user action will retry
}
throw e;
} Prevention
- Build and publish sources/search-index.json in the deploy pipeline.
- Add a deploy smoke test that HEADs the index URL.
- Configure the static server to serve 404 (not HTML rewrites) for missing JSON so failures are explicit.
- Since loadIndex retries after failure, surface a retry affordance in the search UI.
When it happens
Trigger: GET /sources/search-index.json returns a non-2xx status — the index file was not generated/deployed, the path 404s, or the server errors on that route.
Common situations: Deploy pipeline skipped the index build step, the static file is missing from the published assets, an SPA server rewrite returns a 404 for the JSON path, or the site is partially deployed.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Firecrawl search failed: HTTP ${resp.status}
- ${operation} HTTP ${status}: ${safeCode}
- Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d
- Firecrawl scrape failed: HTTP ${resp.status}
- Firecrawl extract failed: HTTP ${resp.status}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/7745377e75fbeca7.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/crawlable-sources-search.mjs:16
// Runs only on the sources directory. Provider details stay on static pages.
function initSourcesSearch() {
const search = document.getElementById('source-search');
const fields = ['domain', 'kind', 'country', 'coverage'].map((name) => document.getElementById('source-' + name));
const results = document.getElementById('source-results');
const grid = document.getElementById('source-catalog');
const more = document.getElementById('source-more');
const note = document.getElementById('source-country-note');
const noResults = document.getElementById('source-no-results');
let indexPromise;
let revision = 0;
let offset = 0;
const loadIndex = () => {
if (!indexPromise) indexPromise = fetch('/sources/search-index.json')
.then((response) => {
if (!response.ok) throw new Error('Search unavailable');
return response.json();
}).catch((error) => { indexPromise = null; throw error; });
return indexPromise;
};
const apply = async (nextPage = false) => {
const request = ++revision;
if (!nextPage) offset = 0;
const query = search.value.trim().toLowerCase();
const [domain, kind, country, coverage] = fields.map((field) => field.value);
grid.replaceChildren();
more.hidden = true;
noResults.hidden = true;
note.hidden = country === 'all' || country === 'intl';
note.textContent = note.hidden ? '' : 'This list shows monitored sources based in the selected country or region. Sources based elsewhere also cover it.';
if (!query && fields.every((field) => field.value === 'all')) {
results.textContent = 'Choose a filter or enter a provider name.';
return;
}View on GitHub (pinned to 7d06c8633d)