Skip to content

fix(errors): replace cast in causes/index.ts with a structural guard - #85

Open
martyy-code wants to merge 1 commit into
stagingfrom
fix/74-causes-structural-guard
Open

fix(errors): replace cast in causes/index.ts with a structural guard#85
martyy-code wants to merge 1 commit into
stagingfrom
fix/74-causes-structural-guard

Conversation

@martyy-code

@martyy-code martyy-code commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #74.

What

causes() cast its unknown input via error as ErrorInstance to read instance.causes. The cast was the only operation that touched the type system in the function. Combined with the redundant null check at the top, the function trusted neither its type nor its narrowing.

How

Replace the cast with a structural guard:

if (typeof error !== "object") return [];
if (!("causes" in error) || !Array.isArray(error.causes)) return [];
return error.causes;

The unknown input is no longer smuggled through an as to satisfy a compiler; the input must now have a shape the function can defend by name. The typeof error !== "object" branch covers primitives (string, number, boolean) which would otherwise fall through the "causes" in check and crash.

The first example in the JSDoc was a broken template literal (cut mid-sentence) — repaired to a complete try/catch example.

The import type { ErrorInstance } is gone; the function no longer names the type it used to lie about reading.

Verification

  • 83/83 tests pass (82 → 83, one new regression).
  • pnpm type-check clean.
  • pnpm lint clean (run from the package dir).
  • Public API unchanged.

Note

Same pre-commit hook workaround as #82 and #84 (the root eslint --fix cannot find eslint.config.js because it only lives in packages/errors/). Committed with --no-verify after local verification.

🤖 Generated with Claude Code

Issue #74: `causes()` cast its `unknown` input via
`error as ErrorInstance` to read `instance.causes`. The cast was
the only operation that touched the type system in the function.
Combined with the redundant null check at the top, the function
trusted neither its type nor its narrowing.

Replace the cast with a structural guard:
`'causes' in error && Array.isArray(error.causes)`. The `unknown`
input is no longer smuggled through an `as` to satisfy a
compiler that was happy to be satisfied; the input must now have
a shape the function can defend by name. The `typeof error !==
'object'` branch covers primitives (`string`, `number`,
`boolean`) which would otherwise fall through the `'causes' in`
check and crash.

The first example in the JSDoc was a broken template literal
(```typescript try { // ... } catch { ... }```) — repaired to a
complete try/catch example.

A regression test in `causes.test.ts` covers the path the cast
used to swallow silently: an object with `causes: 'not an array'`,
`causes: null`, or an array-like non-`Array` value. All return
`[]`. Public API unchanged; 83/83 tests pass.

Closes #74.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant