sickn33/agentic-awesome-skills · info · Error
HTTP ${response.status}: ${response.statusText}
Error message
HTTP ${response.status}: ${response.statusText} What it means
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.
Source
Thrown at skills/fp-async/SKILL.md:84
throw error
}
}
```
### The Solution: Wrap Once, Handle Cleanly
```typescript
import * as TE from 'fp-ts/TaskEither'
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
// One wrapper function - reuse everywhere
const fetchJson = <T>(url: string): TE.TaskEither<Error, T> =>
TE.tryCatch(
async () => {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
(error) => error instanceof Error ? error : new Error(String(error))
)
// AFTER: Clean and composable
const getUser = (userId: string) => fetchJson<User>(`/api/users/${userId}`)
const getPosts = (userId: string) => fetchJson<Post[]>(`/api/users/${userId}/posts`)
```
### tryCatch Explained
`TE.tryCatch` takes two things:
1. An async function that might throw
2. A function to convert the thrown value into your error type
```typescriptView on GitHub (pinned to 58d857988f)
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
Example fix
// before
const res = await fetch(url)
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`)
// after
class HttpError extends Error {
constructor(readonly status: number, readonly statusText: string) {
super(`HTTP ${status}: ${statusText}`)
}
}
const fetchJson = <T>(url: string): TE.TaskEither<HttpError, T> =>
TE.tryCatch(
async () => {
const r = await fetch(url)
if (!r.ok) throw new HttpError(r.status, r.statusText)
return r.json()
},
(e): HttpError => e instanceof HttpError ? e : new HttpError(0, String(e))
) Defensive patterns
Strategy: fallback
Validate before calling
const res = await fetch(url)
if (!res.ok) return { ok: false, status: res.status } as const Type guard
const isHttpTaskError = (e: unknown): e is Error =>
e instanceof Error && /^HTTP \d{3}/.test(e.message) Try / catch
await pipe(
fetchJson<T>(url),
TE.fold(
(e) => { /* Left: HTTP or network failure, e.message has status */ },
(data) => { /* Right: parsed JSON */ }
)
)() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26).
Data as JSON: /api/errors/c27c08e3dbedea6a.
Report an issue: GitHub.