{"record":{"id":"1069a4440eb19fee","repo":"SillyTavern/SillyTavern","slug":"internal-server-error-1069a4","errorCode":null,"errorMessage":"Internal Server Error","messagePattern":"Internal Server Error","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"src/endpoints/horde.js","lineNumber":77,"sourceCode":"    try {\n        const cachedWorkers = cache.get('workers');\n\n        if (cachedWorkers && !request.body.force) {\n            return response.send(cachedWorkers);\n        }\n\n        const agent = await getClientAgent();\n        const fetchResult = await fetch('https://aihorde.net/api/v2/workers?type=text', {\n            headers: {\n                'Client-Agent': agent,\n            },\n        });\n        const data = await fetchResult.json();\n        cache.set('workers', data);\n        return response.send(data);\n    } catch (error) {\n        console.error(error);\n        response.sendStatus(500);\n    }\n});\n\nasync function getHordeTextModelMetadata() {\n    const response = await fetch(HORDE_TEXT_MODEL_METADATA_URL);\n    return await response.json();\n}\n\nasync function mergeModelsAndMetadata(models, metadata) {\n    return models.map(model => {\n        const metadataModel = metadata[model.name];\n        if (!metadataModel) {\n            return { ...model, is_whitelisted: false };\n        }\n        return { ...model, ...metadataModel, is_whitelisted: true };\n    });\n}\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/SillyTavern/SillyTavern/blob/8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8/src/endpoints/horde.js#L59-L95","documentation":"HTTP 500 from POST /api/horde/text-workers in src/endpoints/horde.js:77. The handler fetches https://aihorde.net/api/v2/workers?type=text via node-fetch and returns the JSON; any rejection (DNS failure, connection refused, TCP reset, response.json() parse error) lands in the catch and sendStatus(500) is emitted. There is no retry or fallback.","triggerScenarios":"The server has no internet route to aihorde.net; a corporate proxy/firewall blocks the host; aihorde.net is down or returns non-JSON (HTML error page) so fetchResult.json() throws; getClientAgent() throws because getVersion() fails; cache miss with request.body.force=true forcing a live fetch during an outage.","commonSituations":"Running SillyTavern behind a restrictive firewall or in an offline/air-gapped environment; DNS hiccup; the user clicked 'refresh/force' which bypasses the 60s cache; node-fetch version mismatch causing undici/Headers issues; a transient aihorde.net 5xx that still serves JSON but the connection drops mid-stream.","solutions":["Verify outbound HTTPS to https://aihorde.net/api/v2/workers?type=text works from the host (curl -I).","If behind a proxy, set HTTPS_PROXY/HTTP_PROXY and ensure node-fetch honors it, or whitelist aihorde.net.","Wait and retry: the error is often transient and the 60s cache will serve stale data on the next non-forced call.","Check server console logs (console.error(error)) for the underlying node-fetch error code (ENOTFOUND, ECONNRESET, ETIMEDOUT) to pinpoint DNS vs network vs TLS."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight reachability hint (optional)\nasync function hordeReachable() {\n  try {\n    const r = await fetch('https://aihorde.net/api/v2/status/heartbeat', { method:'HEAD' });\n    return r.ok;\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"// Caller: retry transient horde worker fetch failures\nasync function getTextWorkers(maxAttempts = 3) {\n  for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n    try {\n      const r = await fetch('/api/horde/text-workers', { method:'POST', headers:{'Content-Type':'application/json'}, body:'{}' });\n      if (r.ok) return await r.json();\n      if (r.status === 500 && attempt < maxAttempts) { await new Date(); continue; }\n      throw new Error('text-workers failed: ' + r.status);\n    } catch (e) {\n      if (attempt === maxAttempts) throw e;\n    }\n  }\n}","preventionTips":["Rely on the server cache by not forcing refresh unless necessary.","Monitor aihorde.net status; treat 500 as transient before alerting.","Ensure outbound HTTPS to aihorde.net is permitted by firewall/proxy."],"tags":["network","aihorde","node-fetch","http-500","external-api"],"backgroundTag":null,"analyzedSha":"8172dcd0ee672d3cd9a5e5f7af134f91a45cd2b8","analyzedAt":"2026-08-13T07:48:40.832Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}