{"record":{"id":"c27c08e3dbedea6a","repo":"sickn33/agentic-awesome-skills","slug":"http-response-status-response-statustext-c27c08","errorCode":null,"errorMessage":"HTTP ${response.status}: ${response.statusText}","messagePattern":"HTTP (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"skills/fp-async/SKILL.md","lineNumber":84,"sourceCode":"    throw error\n  }\n}\n```\n\n### The Solution: Wrap Once, Handle Cleanly\n\n```typescript\nimport * as TE from 'fp-ts/TaskEither'\nimport * as E from 'fp-ts/Either'\nimport { pipe } from 'fp-ts/function'\n\n// One wrapper function - reuse everywhere\nconst fetchJson = <T>(url: string): TE.TaskEither<Error, T> =>\n  TE.tryCatch(\n    async () => {\n      const response = await fetch(url)\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n      }\n      return response.json()\n    },\n    (error) => error instanceof Error ? error : new Error(String(error))\n  )\n\n// AFTER: Clean and composable\nconst getUser = (userId: string) => fetchJson<User>(`/api/users/${userId}`)\nconst getPosts = (userId: string) => fetchJson<Post[]>(`/api/users/${userId}/posts`)\n```\n\n### tryCatch Explained\n\n`TE.tryCatch` takes two things:\n1. An async function that might throw\n2. A function to convert the thrown value into your error type\n\n```typescript","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/sickn33/agentic-awesome-skills/blob/58d857988fcfac6986206bca2b2fe223aa437e4b/skills/fp-async/SKILL.md#L66-L102","documentation":"Throw inside the fetchJson TaskEither wrapper in the skill's AFTER example. It fires when response.ok is false, but because the code runs inside TE.tryCatch, the throw is captured into the error channel (Left) instead of escaping — this is the skill's recommended error surface for HTTP failures.","triggerScenarios":"Any GET to the wrapped URL returning 4xx/5xx; e.g. 404 for an unknown resource, 401 from an expired token, 500 from a server fault. The thrown message carries both status and statusText for diagnostics.","commonSituations":"Calling fetchJson against a wrong base URL; forgetting credentials or headers; hitting a CORS-blocked endpoint where the browser reports an opaque failure; JSON parse failure when the body is not JSON.","solutions":["Read the Left value's message to get status + statusText and fix the request accordingly","Confirm URL, headers, and auth token are correct for the failing endpoint","Keep the second tryCatch argument (E.toError-style guard) so non-Error throws are normalized to Error","If you need the status code programmatically, throw a typed error class carrying status instead of a plain Error"],"exampleFix":"// before\nconst res = await fetch(url)\nif (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)\n// after\nclass HttpError extends Error {\n  constructor(readonly status: number, readonly statusText: string) {\n    super(`HTTP ${status}: ${statusText}`)\n  }\n}\nconst fetchJson = <T>(url: string): TE.TaskEither<HttpError, T> =>\n  TE.tryCatch(\n    async () => {\n      const r = await fetch(url)\n      if (!r.ok) throw new HttpError(r.status, r.statusText)\n      return r.json()\n    },\n    (e): HttpError => e instanceof HttpError ? e : new HttpError(0, String(e))\n  )","handlingStrategy":"fallback","validationCode":"const res = await fetch(url)\nif (!res.ok) return { ok: false, status: res.status } as const","typeGuard":"const isHttpTaskError = (e: unknown): e is Error =>\n  e instanceof Error && /^HTTP \\d{3}/.test(e.message)","tryCatchPattern":"await pipe(\n  fetchJson<T>(url),\n  TE.fold(\n    (e) => { /* Left: HTTP or network failure, e.message has status */ },\n    (data) => { /* Right: parsed JSON */ }\n  )\n)()","preventionTips":["Fold over the TaskEither; never try/catch around it","Throw a status-carrying error class if you need to branch on status codes","Test the wrapper against 404, 500, and malformed-JSON bodies"],"tags":["fp-ts","task-either","fetch","http-status"],"backgroundTag":"fetch-non-2xx-response","analyzedSha":"58d857988fcfac6986206bca2b2fe223aa437e4b","analyzedAt":"2026-08-26T11:55:59.350Z","schemaVersion":2},"datasetVersion":"2026-08-26T14:46:13.012Z"}