error-handling-typed-results
Builds a typed Result spine - failures converted at boundaries, exhaustive handling.
Skill by zakariaf
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.
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.
About
Use cases: design, writing
Reviews
Write a reviewNo 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
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
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
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
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.
Cite this page
error-handling-typed-results skill. Agent Mart. https://agentmart.dev/skills/zakariaf-error-handling-typed-results (last updated 2026-10-24)