sickn33/agentic-awesome-skills · error · Error

API error: ${result.left.type}

Error message

API error: ${result.left.type}

What it means

The bridge function in fp-refactor re-throws when the fp-ts TaskEither result is Left, stringifying result.left.type into the message. It exists to show interop between Either-returning code and a legacy throw-based caller during incremental migration.

Source

Thrown at skills/fp-refactor/SKILL.md:1492

```typescript
// Wrap external API calls first
const fetchUserApi = (id: string): TE.TaskEither<ApiError, UserDto> =>
  pipe(
    TE.tryCatch(
      () => externalApiClient.getUser(id),
      (e) => ({ type: 'api_error' as const, cause: e })
    )
  );

// Internal code can stay imperative initially
async function handleUserRequest(userId: string) {
  const result = await fetchUserApi(userId)();
  if (E.isRight(result)) {
    // Process user with existing code
    return processUser(result.right);
  } else {
    throw new Error(`API error: ${result.left.type}`);
  }
}
```

### Strategy 2: Create Bridge Functions

Build helpers to convert between fp-ts and imperative code:

```typescript
// Bridge from Either to thrown errors
const unsafeUnwrap = <E, A>(either: E.Either<E, A>): A =>
  pipe(
    either,
    E.getOrElseW((e) => {
      throw e instanceof Error ? e : new Error(String(e));
    })
  );

View on GitHub (pinned to 58d857988f)

Solutions

  1. Ensure the Left channel always carries a tagged error object with a `type` field so the message is meaningful
  2. Catch this throw at the API boundary and map to an HTTP status instead of letting it 500
  3. Once callers migrate, remove the throw and branch on the Either directly

Example fix

// before
} else {
  throw new Error(`API error: ${result.left.type}`)
}

// after: safe rendering of any Left shape
} else {
  const left = result.left as { type?: string; message?: string }
  throw new Error(`API error: ${left.type ?? left.message ?? 'unknown'}`)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure Left always carries { type: string } before bridging
const tagged = E.mapLeft((e: unknown) =>
  e && typeof e === 'object' && 'type' in e ? e : { type: 'unknown' }
)(result)

Type guard

const isTaggedError = (x: unknown): x is { type: string } =>
  typeof x === 'object' && x !== null && typeof (x as any).type === 'string'

Try / catch

try {
  await handleUserRequest(userId)
} catch (e) {
  // map API error bridge throws to 502/504 with the type preserved
  res.status(502).json({ error: String(e) })
}

Prevention

When it happens

Trigger: handleUserRequest called when fetchUserApi fails — network error, non-2xx API status, or decode failure — so E.isRight(result) is false and the else branch throws.

Common situations: Gradual fp-ts adoption where old layers still expect exceptions; error.type being undefined because the Left holds a plain Error rather than a tagged object, yielding 'API error: undefined'.

Related errors


AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26). Data as JSON: /api/errors/7c5390c80185893f. Report an issue: GitHub.