{"record":{"id":"10ba05f8279419fd","repo":"ramensoftware/windhawk","slug":"request-failed-with-status-status-statustext-statustext","errorCode":null,"errorMessage":"Request failed with status ${status}${statusText ? ` ${statusText}` : ''}","messagePattern":"Request failed with status (.+?)(.+?)` : ''\\}","errorType":"http","errorClass":"HttpError","httpStatus":null,"severity":"error","filePath":"src/windhawk-frontend/apps/windhawk-frontend/src/app/utils/swrHelpers.ts","lineNumber":19,"sourceCode":"/**\n * A response that arrived but reports a failure status. `fetch` resolves for any\n * status the server answers with and rejects only on a network error, so without\n * this the caller would be handed a 404's error page as the document it asked\n * for, and its own error branch would never run.\n */\nexport class HttpError extends Error {\n  readonly status: number;\n\n  constructor(status: number, statusText?: string) {\n    super(`Request failed with status ${status}${statusText ? ` ${statusText}` : ''}`);\n    this.name = 'HttpError';\n    this.status = status;\n  }\n}\n\nfunction assertOk(response: Response): Response {\n  if (!response.ok) {\n    throw new HttpError(response.status, response.statusText);\n  }\n  return response;\n}\n\nexport const fetchText = (input: RequestInfo | URL, init?: RequestInit) =>\n  fetch(input, init).then((res) => assertOk(res).text());\n\nexport const fetchJson = <T = unknown>(input: RequestInfo | URL, init?: RequestInit): Promise<T> =>\n  fetch(input, init).then((res) => assertOk(res).json());\n\nconst CATALOG_BASE_URL = 'https://mods.windhawk.net/';\n\n/**\n * A language tag, which is the only thing that names a catalog file. Anything\n * else is kept out of the URL, where a `/`, a `..` or a `?` would make it ask\n * for a different document than the one the path spells out.\n */\nconst LANGUAGE_TAG = /^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{1,8})*$/;","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/ramensoftware/windhawk/blob/61d99ed8e182e1af1b60109612b6763ad1b4b74e/src/windhawk-frontend/apps/windhawk-frontend/src/app/utils/swrHelpers.ts#L1-L37","documentation":"assertOk() wraps the fetch Response in swrHelpers and throws an HttpError carrying the numeric status and statusText whenever response.ok is false. All helpers (fetchText, fetchJson, fetchDefaultCatalog, fetchCatalogJson) funnel through it, so any non-2xx HTTP response becomes this error.","triggerScenarios":"fetchText/fetchJson/fetchDefaultCatalog/fetchCatalogJson called against a URL returning 404/500/401 etc., e.g. the catalog JSON endpoint is down or the path moved.","commonSituations":"Catalog server offline, wrong base URL, expired auth, CDN 404 after a release renamed the catalog file.","solutions":["Log error.status to identify the exact HTTP failure and fix the URL/request accordingly","Verify the endpoint is reachable (curl the URL) and the server is healthy","Add retry/fallback logic around catalog fetches for transient 5xx"],"exampleFix":"// before\nconst text = await fetchText(url);\n// after\ntry {\n  const text = await fetchText(url);\n} catch (e) {\n  if (e instanceof HttpError && e.status === 404) useFallbackCatalog();\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"const res = await fetch(url, { method: 'HEAD' });\nif (!res.ok) throw new Error(`catalog endpoint unhealthy: ${res.status}`);","typeGuard":"class HttpError extends Error { constructor(public status: number, statusText: string) { super(statusText); } }\nconst isHttpError = (e: unknown): e is HttpError => e instanceof HttpError;","tryCatchPattern":"try { const data = await fetchJson(url); } catch (e) { if (isHttpError(e)) { if (e.status >= 500) retryLater(); else reportBadUrl(e.status); } else throw e; }","preventionTips":["Check response.status before consuming parsed data","Use HEAD/health checks before dependent fetches","Centralize fetch handling through assertOk so all errors are HttpError instances"],"tags":["http","fetch","network"],"backgroundTag":"http-error-response","analyzedSha":"61d99ed8e182e1af1b60109612b6763ad1b4b74e","analyzedAt":"2026-09-12T14:02:41.115Z","contentChangedAt":"2026-09-12T14:02:41.115Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}