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
- Ensure the Left channel always carries a tagged error object with a `type` field so the message is meaningful
- Catch this throw at the API boundary and map to an HTTP status instead of letting it 500
- 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
- Standardize Left payloads as tagged objects with a type field
- Migrate callers to branch on Either directly and delete throw bridges
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
- HTTP ${response.status}
- HTTP ${response.status}: ${response.statusText}
- Order not found
- Not found
- No order
AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26).
Data as JSON: /api/errors/7c5390c80185893f.
Report an issue: GitHub.