datawhalechina/hello-agents · critical
No reachable backend API. Tried
Error message
No reachable backend API. Tried: ${candidateBases.join(', ')} What it means
Backend discovery failure: at startup the client builds candidateBases (VITE_API_BASE_URL env var, then http://127.0.0.1:8000/api, http://localhost:8000/api, and ${protocol}//${currentHost}:8000/api) and probes each with GET {base}/health (3s timeout) until one answers. If none do, it throws listing all tried bases. The result is cached in activeBase, so one failure is retried on every call.
Solutions
- Verify the backend serves GET /health: curl -i http://127.0.0.1:8000/api/health (note the /api prefix is part of base)
- Set VITE_API_BASE_URL correctly at build time to the publicly reachable, https API origin
- Ensure /health exists, returns 200 quickly, and is exempt from auth
- For https deployments, serve the API over https or proxy it under the same origin so probes aren't blocked as mixed content
Example fix
// before
bases.push('http://127.0.0.1:8000/api');
bases.push('http://localhost:8000/api');
bases.push(`${protocol}//${currentHost}:8000/api`);
// after (prefer same-origin proxy in prod; skip http candidates on https pages)
if (protocol !== 'https:') {
bases.push('http://127.0.0.1:8000/api', 'http://localhost:8000/api', `${protocol}//${currentHost}:8000/api`);
} else {
bases.push(`${protocol}//${currentHost}/api`); // reverse-proxied same origin
} Defensive patterns
Strategy: fallback
Validate before calling
async function probe(base) { try { const r = await axios.get(`${base}/health`, { timeout: 3000 }); return r.status === 200; } catch { return false; } } const ok = await probe(VITE_API_BASE_URL ?? defaultBase); if (!ok) showSetupError(); Try / catch
try { await apiGet('/report'); } catch (e) { if (e.message.startsWith('No reachable backend')) showBackendDownBanner(); else throw e; } Prevention
- Set VITE_API_BASE_URL correctly at build time for deployed environments
- Serve /health unauthenticated, fast, and with CORS enabled
- Avoid http:// candidates on https pages (mixed content)
- Clear/re-probe activeBase on failure instead of caching forever
When it happens
Trigger: Backend not running on port 8000; backend running but /health route missing or erroring; VITE_API_BASE_URL set to a wrong/stale URL; accessing from another device where 127.0.0.1/localhost point nowhere and currentHost:8000 is firewalled; https page probing http:// candidates gets blocked by mixed-content rules.
Common situations: Deployed frontend (https) cannot probe http://127.0.0.1:8000 (mixed content) and backend host:8000 is not exposed publicly; VITE_API_BASE_URL typo'd during build; backend started without the /health endpoint; corporate network blocks non-standard ports.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/34f0b30d10e264fa.
Report an issue: GitHub.
Appendix: source
Thrown at Co-creation-projects/monkeyhlj-NetworkHealthReportAgent/frontend/src/api.js:40
const api = axios.create({ timeout: API_TIMEOUT })
async function resolveBase() {
if (activeBase) {
return activeBase
}
for (const base of candidateBases) {
try {
await axios.get(`${base}/health`, { timeout: 3000 })
activeBase = base
return base
} catch (_) {
// Try next candidate
}
}
throw new Error(`No reachable backend API. Tried: ${candidateBases.join(', ')}`)
}
async function apiGet(path, config = {}) {
const base = await resolveBase()
return api.get(`${base}${path}`, config)
}
async function apiPost(path, body = {}, config = {}) {
const base = await resolveBase()
return api.post(`${base}${path}`, body, config)
}
export async function fetchSites() {
const { data } = await apiGet('/sites')
return data.sites
}
export async function fetchReport(siteId, startDate, endDate) {View on GitHub (pinned to 606a07d341)