Skip to content
Agentmart
Submit a tool

error-handling-typed-results

Builds a typed Result spine - failures converted at boundaries, exhaustive handling.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Chalk Labs

Need help deploying error-handling-typed-results?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-error-handling-typed-results

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Builds a typed Result spine - failures converted at boundaries, exhaustive handling. Use when writing try/catch, .catch or a Result, or fixing swallowed errors or unhandled rejections. Not for error UI (ui-states-and-feedback).

Use cases: design, writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/error-handling-typed-results

What's inside

Skill name
error-handling-typed-results
Description
Builds a typed Result spine - failures converted at boundaries, exhaustive handling. Use when writing try/catch, .catch or a Result, or fixing swallowed errors or unhandled rejections. Not for error UI (ui-states-and-feedback).
License
MIT
Instructions
238 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (56)

  • SKILL.md
  • shared.json · assets/
  • connect-error-log.ts · examples/
  • haptics-adapter.ts · examples/
  • root-layout.tsx · examples/
  • task-failure-message.ts · examples/tasks/
  • task-failure.ts · examples/tasks/
  • task-repository.test.ts · examples/tasks/
  • task-repository.ts · examples/tasks/
  • to-storage-failure.test.ts · examples/tasks/
  • to-storage-failure.ts · examples/tasks/
  • use-add-task-model.test.ts · examples/tasks/
  • use-add-task-model.ts · examples/tasks/
  • boundaries-and-catches.md · references/
  • checker-rules.md · references/
  • global-error-net.md · references/
  • never-lose-data.md · references/
  • result-and-failures.md · references/
  • test-recipes.md · references/
  • check-error-net.mjs · scripts/
  • check-error-spine.mjs · scripts/
  • check-lib.mjs · scripts/
  • error-scan.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • result.test.ts · templates/src/core/
  • result.ts · templates/src/core/
  • crash-fallback.test.tsx · templates/src/features/crash-recovery/
  • crash-fallback.tsx · templates/src/features/crash-recovery/
  • crash-screen.test.tsx · templates/src/features/crash-recovery/
  • crash-screen.tsx · templates/src/features/crash-recovery/
  • create-route-error-boundary.test.tsx · templates/src/features/crash-recovery/
  • create-route-error-boundary.tsx · templates/src/features/crash-recovery/
  • app-error-net.test.ts · templates/src/services/error-net/
  • app-error-net.ts · templates/src/services/error-net/
  • error-net.test.ts · templates/src/services/error-net/
  • error-net.ts · templates/src/services/error-net/
  • install-error-net.test.ts · templates/src/services/error-net/
  • install-error-net.ts · templates/src/services/error-net/
  • package.json · tests/bases/app/
  • index.tsx · tests/bases/app/src/app/
  • task.ts · tests/bases/app/src/core/tasks/
  • sql-driver.ts · tests/bases/app/src/data/
  • en.json · tests/bases/app/src/i18n/catalogs/
  • translate-now.ts · tests/bases/app/src/i18n/
  • error-log-port.ts · tests/bases/app/src/services/error-log/
  • haptics-port.ts · tests/bases/app/src/services/haptics/
  • app-text.tsx · tests/bases/app/src/ui/
  • tsconfig.json · tests/bases/app/
  • memory-error-log-adapter.ts · tests/bases/canonical-error-log/src/services/error-log/
  • CASES.json · tests/fixtures/check-error-net/
  • SUITE.json · tests/fixtures/check-error-net/
  • EXPECT.txt · tests/fixtures/check-error-net/good/
  • CASES.json · tests/fixtures/check-error-spine/
  • SUITE.json · tests/fixtures/check-error-spine/
  • EXPECT.txt · tests/fixtures/check-error-spine/good/

SKILL.md (first lines)

# Error handling with typed results

Expected failures travel as typed values (`Result<T, F>` with a `kind` union) that every caller must
handle; only bugs throw, and those land in one global error net and a recording error boundary. No error
path ever destroys the user's data. Two zero-dependency checkers prove what ESLint and `tsc` cannot see.

## Rules that must hold

1. **Expected failures are values; only bugs throw.** Anything that can happen in a correct program (not
   found, offline, storage full, invalid input, cancelled, a corrupt row) returns `Result<T, F>`; a broken
   invariant throws `new Error(message, { cause })`. Why: the type system then forces every caller to handle
   every expected case, and a thrown expected failure crashes a handler or is swallowed by a caller.
2. **One Result type, from `src/core/result.ts`.** `{ ok: true, value } | { ok: false, error }` with `ok`,
   `err`, `attempt`, `attemptAsync`; no second Result/Either and no result library. Why: one vocabulary
   from core to screen; two shapes need adapter code everywhere they meet.
3. **A failure is a kebab-case `kind` with typed params, never text or an Error.** `err({ kind:
   'task-title-taken', title })`; never `err('...')`, `err(new Error())`, `Result<T, string | Error |
   unknown>`, nor a `message` in a failure. Why: a string cannot be switched on exhaustively or translated,
   and baked-in English breaks an Arabic UI.
4. **Convert at the boundary, once.** Adapters, repositories, the ApiClient and parsers catch the vendor's
   error and return the boundary's failure family; no `await` in an adapter escapes outside try/catch or
   `attemptAsync`. Why: above the boundary nobody knows SQLite or StoreKit error shapes, and an escaped
   rejection is dropped silently in release builds.
5. **Every catch maps, records with a fallback, or rethrows with `{ cause }`.** Console-only, comment-only
   and `.catch(() => undefined)` handlers fail; a cosmetic effect may ignore its failure only inside its
   adapter with a `// best-effort: <reason>` comment. Why: nobody reads the console on a user's phone, so
   a swallowed error leaves no trace, and a rethrow without `cause` loses the original stack.
6. **Record the unexpected, with the original error, before returning the failure.** The error log gets
   the raw error; expected failures (user input, offline) are not recorded. Why: after the conversion only
   `{ kind, params }` exists, and a log full of expected failures hides real defects.
7. **Handle every failure exhaustively and never drop a Result.** Branch on `error.kind` with a `switch`
   or a `Record<F['kind'], ...>` table, never an `if/else` whose `else` absorbs future kinds; a call that
   returns a Result is never a bare statement (`if (x) save(t);` included), a `forEach` callback's body or
   an `on*` arrow body. Why: a new kind becomes a compile error everywhere it must be handled, and
   `no-floating-promises` cannot see a dropped `Result`.
8. **The UI shows a message chosen by kind, never `error.message`.** One table per failure family maps
   each kind to a catalog key (params become ICU values) and every key exists in every locale. Why:
   `error.message` is English, technical and can hold user data; a missing key renders a raw id.
9. **One global error net, installed by the root layout's first app import and connected by the
   composition root.** It forwards to the handler it replaced, enables Hermes rejection tracking in every
   build (nothing in the net tests `__DEV__`), buffers until the composition root connects the error log
   (before any provider renders), and calls its sink inside try/catch. Why: React Native 0.86 tracks
   unhandled rejections only in `__DEV__`,
   a module that throws while loading runs before any install call in a module body, and a net nobody
   connects keeps every report in memory until the process ends.
10. **The root route exports an `ErrorBoundary` that records the error and offers one way out.** It never
    retries or navigates away from an effect or a timer, and its fallback stays trivial and reads no
    context that lacks a default. Why: render errors never reach the net (React Native reports them
    straight to its ExceptionsManager) and Expo Router's boundary records nothing, so its `record` is the
    only record; an automatic retry of a repeating render error is a crash loop; and the fallback renders
    outside every provider, where an error it throws is a fatal crash nobody records.
11. **No error path destroys data.** No delete, drop, clear, truncate or reset inside a catch, a `.catch`
    handler or a failure branch; a bad record is quarantined and the rest loads; data newer than the app
    is left untouched. Why: "reset on error" turns a recoverable failure into permanent loss.
12. **A failed user write keeps the input and says so.** The model clears a form only after `ok`; a
    failure becomes a message with a retry; user writes are never fire-and-forget. Why: a silent failed
    save is data loss the user discovers days later.

## Workflow

1. **Choose the mechanism** for each failure with the table in
   [references/result-and-failures.md](references/result-and-failures.md) (section 1): Result, `T | null`,
   throw, boundary or net.
2. **Add the Result module** when `src/core/result.ts` is missing: copy `templates/src/core/result.ts`
   and its test (line 1 names the path). Other skills cite this file; extend it in place (a helper earns
   its place at three call sites, with a test), never with a second Result module.
3. **Design one failure family per boundary**, imitating
   [examples/tasks/task-failure.ts](examples/tasks/task-failure.ts) (sections 4 and 5 of the result
   reference: kinds, params, layering).
4. **Convert at the boundary**: read [references/boundaries-and-catches.md](references/boundaries-and-catches.md),
   imitate [examples/tasks/to-storage-failure.ts](examples/tasks/to-storage-failure.ts) (and its test on
   real `node:sqlite` errors) and [examples/tasks/task-repository.ts](examples/tasks/task-repository.ts) (and its test);
   wrap adapter calls with `attemptAsync`, and a call that can hang with async-safety's
   `withTimeout((signal) => Promise<Result>, afterMs, signal?)` (section 7); a cosmetic adapter follows
   [examples/haptics-adapter.ts](examples/haptics-adapter.ts).
5. **Handle and present**: switch or table on `kind`; add a message table like
   [examples/tasks/task-failure-message.ts](examples/tasks/task-failure-message.ts), put its keys in every
   catalog, and add the per-locale test from [references/test-recipes.md](references/test-recipes.md)
   (section 2). Screens call a void intent of the model hook, never a Result function.
6. **Protect the user's data**: follow [references/never-lose-data.md](references/never-lose-data.md);
   the model keeps input until `ok`, as [examples/tasks/use-add-task-model.ts](examples/tasks/use-add-task-model.ts)
   and its test show.
7. **Install the global net** (once per app): copy `templates/src/services/error-net/` (`error-net.ts`,
   `app-error-net.ts`, `install-error-net.ts`, each with its test); make
   `import '@/services/error-net/install-error-net';` the first app import of `src/app/_layout.tsx`
   (or the line after the Intl polyfills in `index.ts`); connect the error log right after the composition
   root creates the services, in the root layout's `useState` initializer, before any provider renders
   ([examples/root-layout.tsx](examples/root-layout.tsx), [examples/connect-error-log.ts](examples/connect-error-log.ts)).
   Read [references/global-error-net.md](references/global-error-net.md) (section 5 for connecting and
   durability) before changing the net, and section 8 there when the app ships a crash reporter.
8. **Add the root error boundary**: copy `templates/src/features/crash-recovery/`
   (`create-route-error-boundary.tsx`, `crash-screen.tsx`, `crash-fallback.tsx`, their tests). The
   fallback reads no provider: the background from `useTheme()` (its context defaults to the light
   theme) and copy from `createTranslateNow(onError)` in `src/i18n/translate-now.ts`
   (i18n-strings-and-catalogs owns it; it keeps the language the app showed last), wrapped in
   `TextLocaleProvider` with that same `languageNow()` language and `directionOf(language)`, so an RTL
   crash text is laid out right to left (rtl-and-direction's `root-direction-provider`). Put `crash.title`,
   `crash.body` and `crash.restart` in every catalog (`tsc` fails on a missing source key). Export
   `ErrorBoundary` from the root layout with `record` reaching `appErrorNet.report` and
   `restart: () => reloadAppAsync(...)`. The tests import unit-and-component-tests' router helper
   (`test/render-router-with-providers.tsx`) and accessibility-as-code's audit
   (`test/a11y/find-inaccessible-pressables.ts`); when either is missing, set it up with that skill
   first. They render the boundary and the fallback with no provider on purpose, under the documented
   `// no-providers: <why>` marker (section 5 of the test recipes).
9. **Test**: the recipes in [references/test-recipes.md](references/test-recipes.md); then `npm run
   typecheck`, `npx eslint . --max-warnings 0` and `npx jest --ci`.
10. **Run the checkers** from the repo root; look up any rule in
    [references/checker-rules.md](references/checker-rules.md); fix and rerun until both print
    `RESULT: PASS`.
11. **On a device** (once per release candidate, by hand): prove a rejection, a render error and a
    handler error each reach the error log in a release build on a named simulator or emulator (recipe in
    section 10 of the net reference).

## Definition of done

- [ ] `src/core/result.ts` comes from the template (any helper added there, with a test); no other Result/Either type and no result library.
- [ ] Every expected failure is a `kind` union with typed params; no `err('...')`, `err(new Error())`, `Result<T, string | Error | unknown>` or `message` field.
- [ ] Each boundary converts once, records unexpected failures with the original error, and no adapter `await` escapes.
- [ ] Every catch maps, records with a fallback, or rethrows with `{ cause }`; `// best-effort:` appears only in adapters.

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (14 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get error-handling-typed-results running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying error-handling-typed-results?

CallGet help
Cite this page

error-handling-typed-results skill. Agent Mart. https://agentmart.dev/skills/zakariaf-error-handling-typed-results (last updated 2026-10-24)