{"record":{"id":"fae7b37c5c146df0","repo":"hcengineering/platform","slug":"failed-to-fetch-config","errorCode":null,"errorMessage":"Failed to fetch config","messagePattern":"Failed to fetch config","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"foundations/core/packages/api-client/src/config.ts","lineNumber":31,"sourceCode":"// limitations under the License.\n//\n\nimport { concatLink } from '@hcengineering/core'\n\nexport interface ServerConfig {\n  ACCOUNTS_URL: string\n  COLLABORATOR_URL: string\n  FILES_URL: string\n  UPLOAD_URL: string\n}\n\nexport async function loadServerConfig (url: string): Promise<ServerConfig> {\n  const configUrl = concatLink(url, '/config.json')\n  const res = await fetch(configUrl, { keepalive: true })\n  if (res.ok) {\n    return (await res.json()) as ServerConfig\n  }\n  throw new Error('Failed to fetch config')\n}\n","sourceCodeStart":13,"sourceCodeEnd":33,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/core/packages/api-client/src/config.ts#L13-L33","documentation":"loadServerConfig fetches <server-url>/config.json, which describes service endpoints (accounts, collaborator, etc.). If the fetch response is not ok (404, 500, etc.), it throws 'Failed to fetch config'. The client cannot discover service URLs without this file.","triggerScenarios":"Calling connect/getWorkspaceToken against a URL where /config.json is missing, returns 404, or the server is down/proxy returns an error page.","commonSituations":"Pointing the client at a wrong or non-Huly frontend URL, server behind a proxy that strips/rewrites config.json, deployment where static config.json was not deployed, network outage or DNS misconfiguration, CORS-blocked fetch failing the response.","solutions":["Open <url>/config.json in a browser/curl and confirm it returns valid JSON with 200.","Correct the base URL passed to connect/loadServerConfig.","Check the server deployment includes config.json and the reverse proxy routes it correctly.","Check network/DNS/CORS issues preventing the fetch."],"exampleFix":"// before\nconst client = await connect('https://wrong-host.example.com', { workspace: 'ws' })\n// after\nconst res = await fetch('https://huly.example.com/config.json')\nif (!res.ok) throw new Error('config.json unreachable: ' + res.status)\nconst client = await connect('https://huly.example.com', { workspace: 'ws' })","handlingStrategy":"retry","validationCode":"const res = await fetch(new URL('/config.json', baseUrl), { method: 'HEAD' })\nif (!res.ok) throw new Error(`Server at ${baseUrl} is not a Huly frontend (config.json ${res.status})`)","typeGuard":"function isServerConfig(v: unknown): v is ServerConfig {\n  return typeof v === 'object' && v !== null && 'ACCOUNTS_URL' in v\n}","tryCatchPattern":"async function loadConfigWithRetry(url: string, attempts = 3): Promise<ServerConfig> {\n  for (let i = 0; i < attempts; i++) {\n    try { return await loadServerConfig(url) }\n    catch (e) { if (i === attempts - 1) throw new Error(`Cannot reach ${url}/config.json — check the URL, proxy and network`, { cause: e }) ; await sleep(1000 * 2 ** i) }\n  }\n  throw new Error('unreachable')\n}","preventionTips":["Verify <base-url>/config.json returns 200 JSON during deployment smoke tests","Never hardcode a guessed host; keep the server URL in validated env config","Check reverse-proxy rules preserve /config.json routing"],"tags":["network","configuration","http"],"backgroundTag":"config-fetch-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}