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

```typescript

View on GitHub (pinned to 58d857988f)

Solutions

  1. Read the Left value's message to get status + statusText and fix the request accordingly
  2. Confirm URL, headers, and auth token are correct for the failing endpoint
  3. Keep the second tryCatch argument (E.toError-style guard) so non-Error throws are normalized to Error
  4. 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

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.