Crosstalk-Solutions/project-nomad · error
Could not fetch directory listing from the provided URL
Error message
Could not fetch directory listing from the provided URL
What it means
Catch-all 502 from browseLibrary when fetching the directory listing from the provided library URL fails for a non-SSRF reason: host unreachable, connection refused, timeout, TLS error, or the response is not a parseable directory index. The proxy-style 502 signals the failure belongs to the upstream URL, not this server.
Source
Thrown at admin/app/controllers/zim_controller.ts:224
await this.zimService.removeCustomLibrary(payload.params.id)
return { message: 'Custom library removed' }
} catch (error) {
if (error.message === 'Custom library not found') {
return response.status(404).send({ message: error.message })
}
throw error
}
}
async browseLibrary({ request, response }: HttpContext) {
const payload = await request.validateUsing(browseLibraryValidator)
try {
return await this.zimService.browseLibraryUrl(payload.url)
} catch (error) {
if (error.message?.includes('loopback or link-local')) {
return response.status(400).send({ message: error.message })
}
return response.status(502).send({
message: 'Could not fetch directory listing from the provided URL',
})
}
}
}
View on GitHub (pinned to 0bd1c6f4f9)
Solutions
- curl the URL from the admin server host to confirm it is reachable and returns a plain directory listing (links ending in .zim)
- Fix scheme/port/hostname typos and ensure the server serves an autoindex-style listing the parser understands
- If TLS fails, install/point to a valid certificate or use http within a trusted network
- Increase the fetch timeout in zimService.browseLibraryUrl for slow mirrors
Example fix
// before
await zimController.browseLibrary({ url: 'https://mirror.example.org/zims/' }) // 502
// after
# verify from the same host first:
# curl -sSL https://mirror.example.org/zims/ | head
await zimController.browseLibrary({ url: 'https://mirror.example.org/en/zims/' }) // valid listing path Defensive patterns
Strategy: fallback
Validate before calling
// pre-flight the URL from the same host/network before calling browse
const ok = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) })
if (!ok.ok) throw new Error(`Library URL unreachable (${ok.status})`) Try / catch
try {
return await zimService.browseLibraryUrl(url)
} catch (e) {
if (e.message?.includes('loopback or link-local')) throw e // SSRF — do not retry
// fallback: try a mirror or cached listing
return cachedListing(url) ?? []
} Prevention
- curl/HEAD-check library URLs before indexing
- Monitor library mirrors for availability
- Prefer stable mirrors and set sane fetch timeouts
When it happens
Trigger: Calling browse-library with a URL that is down/unreachable, serves a non-HTML/non-directory-index page, requires auth, or has a bad certificate; any fetch error not matching the loopback/link-local message.
Common situations: Library server stopped or firewall blocking the admin server's egress; URL points to an HTML page or a native OPDS feed instead of an Apache/nginx-style directory listing; self-signed cert causing TLS rejection; slow mirror timing out.
Related errors
- ${error.message}
- HTTP ${resp.status} from ${MANIFEST_URL}
- No internet — connect to download FDA drug data. (${msg})
- Failed to download tar file
- Base map assets are missing and could not be downloaded. Ple
AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27).
Data as JSON: /api/errors/edfe3b61396c5578.
Report an issue: GitHub.