@skill-graph/cli 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +247 -0
- package/LICENSE +200 -0
- package/NOTICE +62 -0
- package/README.md +398 -0
- package/SKILL_GRAPH.md +443 -0
- package/bin/skill-graph.js +374 -0
- package/docs/ADOPTION.md +117 -0
- package/docs/CONFORMANCE.md +66 -0
- package/docs/PRIMER.md +384 -0
- package/docs/QUICKSTART-30MIN.md +333 -0
- package/docs/ROUTING-METRICS.md +120 -0
- package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
- package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
- package/docs/SKILL_AUDIT_LOOP.md +195 -0
- package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
- package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
- package/docs/adr/0001-predicate-set.md +69 -0
- package/docs/adr/0002-json-ld-context.md +82 -0
- package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
- package/docs/adr/0004-persistent-identifiers.md +74 -0
- package/docs/adr/0005-freshness-consolidation.md +70 -0
- package/docs/adr/0006-revise-predicate-rename.md +105 -0
- package/docs/adr/0007-audit-loop-cadence.md +99 -0
- package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
- package/docs/category-consumers.md +168 -0
- package/docs/concept-map.md +194 -0
- package/docs/diagrams/drift-states.mmd +21 -0
- package/docs/diagrams/manifest-pipeline.mmd +25 -0
- package/docs/diagrams/routing-harness.mmd +41 -0
- package/docs/diagrams/starter-graph.mmd +53 -0
- package/docs/field-decision-guide.md +315 -0
- package/docs/field-rationale.md +211 -0
- package/docs/field-reference.generated.md +624 -0
- package/docs/field-reference.md +1426 -0
- package/docs/glossary.md +190 -0
- package/docs/head-noun-glossary.md +63 -0
- package/docs/images/audit-phases.png +0 -0
- package/docs/images/drift-states.png +0 -0
- package/docs/images/graded-mode.png +0 -0
- package/docs/images/manifest-pipeline.png +0 -0
- package/docs/images/routing-harness.png +0 -0
- package/docs/images/skill-anatomy.png +0 -0
- package/docs/images/starter-graph.png +0 -0
- package/docs/images/system-model.png +0 -0
- package/docs/integrations/github-actions.md +155 -0
- package/docs/manifest-field-mapping.md +443 -0
- package/docs/marketplace-publication-queue.generated.md +240 -0
- package/docs/marketplace-release-agent-prompt.md +82 -0
- package/docs/marketplace-skill-candidate-list.md +272 -0
- package/docs/marketplace-syndication.md +222 -0
- package/docs/migration-sample-review.md +155 -0
- package/docs/migrations/v4-to-v5.md +168 -0
- package/docs/migrations/v5-to-v6.md +221 -0
- package/docs/name-exceptions.yaml +37 -0
- package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
- package/docs/plans/multi-root-workspace.md +148 -0
- package/docs/plans/scripts-roadmap.md +107 -0
- package/docs/plans/v4-schema-bump.md +160 -0
- package/docs/plans/wave-2-extraction.md +122 -0
- package/docs/positioning-vs-marketplaces.md +175 -0
- package/docs/proposals/skill-audit-loop-positioning.md +160 -0
- package/docs/quality-doctrine.md +138 -0
- package/docs/recommended-skills.md +150 -0
- package/docs/research/skill-comprehension-eval-research.md +1830 -0
- package/docs/research/skill-retrieval-evidence.md +66 -0
- package/docs/skill-metadata-protocol.md +471 -0
- package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
- package/examples/audits/a11y/findings.md +52 -0
- package/examples/audits/a11y/scorecard.md +21 -0
- package/examples/audits/a11y/verdict.md +44 -0
- package/examples/audits/debugging/findings.md +59 -0
- package/examples/audits/debugging/scorecard.md +22 -0
- package/examples/audits/debugging/verdict.md +33 -0
- package/examples/audits/documentation/findings.md +59 -0
- package/examples/audits/documentation/scorecard.md +22 -0
- package/examples/audits/documentation/verdict.md +33 -0
- package/examples/evals/a11y.json +140 -0
- package/examples/evals/api-design.json +52 -0
- package/examples/evals/code-review.json +52 -0
- package/examples/evals/data-modeling.json +52 -0
- package/examples/evals/database-migration.json +52 -0
- package/examples/evals/debugging.json +118 -0
- package/examples/evals/dependency-architecture.json +52 -0
- package/examples/evals/design-system-architecture.json +52 -0
- package/examples/evals/error-tracking.json +52 -0
- package/examples/evals/event-contract-design.json +52 -0
- package/examples/evals/form-ux-architecture.json +52 -0
- package/examples/evals/framework-fit-analysis.json +52 -0
- package/examples/evals/graph-audit.json +139 -0
- package/examples/evals/information-architecture.json +52 -0
- package/examples/evals/interaction-feedback.json +52 -0
- package/examples/evals/interaction-patterns.json +52 -0
- package/examples/evals/layout-composition.json +52 -0
- package/examples/evals/lint-overlay.json +117 -0
- package/examples/evals/microcopy.json +52 -0
- package/examples/evals/observability-modeling.json +52 -0
- package/examples/evals/pattern-recognition.json +96 -0
- package/examples/evals/performance-engineering.json +52 -0
- package/examples/evals/refactor.json +128 -0
- package/examples/evals/semiotics.json +52 -0
- package/examples/evals/skill-infrastructure.json +96 -0
- package/examples/evals/skill-router.json +140 -0
- package/examples/evals/skill-router.routing.json +113 -0
- package/examples/evals/system-interface-contracts.json +52 -0
- package/examples/evals/task-analysis.json +52 -0
- package/examples/evals/testing-strategy.json +118 -0
- package/examples/evals/type-safety.json +249 -0
- package/examples/evals/visual-design-foundations.json +52 -0
- package/examples/evals/webhook-integration.json +52 -0
- package/examples/exports/a11y.skill-md.md +80 -0
- package/examples/exports/debugging.skill-md.md +80 -0
- package/examples/exports/refactor.skill-md.md +78 -0
- package/examples/exports/testing-strategy.skill-md.md +81 -0
- package/examples/projects/markdown-static-site/README.md +115 -0
- package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
- package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
- package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
- package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
- package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
- package/examples/projects/saas-stripe-postgres/README.md +208 -0
- package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
- package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
- package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
- package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
- package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
- package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
- package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
- package/examples/protocol/skill-metadata-template.md +301 -0
- package/examples/protocol/skills.manifest.sample.json +13245 -0
- package/examples/skill-metadata-template.md +317 -0
- package/examples/skills.manifest.sample.json +13519 -0
- package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
- package/marketplace/README.md +17 -0
- package/marketplace/skills/a11y/SKILL.md +66 -0
- package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
- package/marketplace/skills/agent-engineering/SKILL.md +386 -0
- package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
- package/marketplace/skills/ai-native-development/SKILL.md +294 -0
- package/marketplace/skills/api-design/SKILL.md +60 -0
- package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
- package/marketplace/skills/background-jobs/SKILL.md +265 -0
- package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
- package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
- package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
- package/marketplace/skills/code-review/SKILL.md +120 -0
- package/marketplace/skills/color-system-design/SKILL.md +43 -0
- package/marketplace/skills/component-architecture/SKILL.md +126 -0
- package/marketplace/skills/compression/SKILL.md +112 -0
- package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
- package/marketplace/skills/connection-pooling/SKILL.md +105 -0
- package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
- package/marketplace/skills/content-monitor/SKILL.md +209 -0
- package/marketplace/skills/context-engineering/SKILL.md +320 -0
- package/marketplace/skills/context-graph/SKILL.md +174 -0
- package/marketplace/skills/context-management/SKILL.md +174 -0
- package/marketplace/skills/context-window/SKILL.md +239 -0
- package/marketplace/skills/contract-testing/SKILL.md +120 -0
- package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
- package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
- package/marketplace/skills/data-modeling/SKILL.md +59 -0
- package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
- package/marketplace/skills/database-migration/SKILL.md +429 -0
- package/marketplace/skills/debugging/SKILL.md +67 -0
- package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
- package/marketplace/skills/design-module-composition/SKILL.md +43 -0
- package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
- package/marketplace/skills/design-thinking/SKILL.md +44 -0
- package/marketplace/skills/diagnosis/SKILL.md +296 -0
- package/marketplace/skills/diff-analysis/SKILL.md +188 -0
- package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
- package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
- package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
- package/marketplace/skills/error-boundary/SKILL.md +235 -0
- package/marketplace/skills/error-tracking/SKILL.md +261 -0
- package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
- package/marketplace/skills/evaluation/SKILL.md +113 -0
- package/marketplace/skills/event-contract-design/SKILL.md +60 -0
- package/marketplace/skills/event-storming/SKILL.md +56 -0
- package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
- package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
- package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
- package/marketplace/skills/generative-ui/SKILL.md +118 -0
- package/marketplace/skills/graph-audit/SKILL.md +81 -0
- package/marketplace/skills/guardrails/SKILL.md +118 -0
- package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
- package/marketplace/skills/http-semantics/SKILL.md +136 -0
- package/marketplace/skills/ideation/SKILL.md +41 -0
- package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
- package/marketplace/skills/information-architecture/SKILL.md +59 -0
- package/marketplace/skills/integration-test-design/SKILL.md +111 -0
- package/marketplace/skills/intent-recognition/SKILL.md +136 -0
- package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
- package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
- package/marketplace/skills/journey-mapping/SKILL.md +41 -0
- package/marketplace/skills/keywords/SKILL.md +213 -0
- package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
- package/marketplace/skills/layout-composition/SKILL.md +59 -0
- package/marketplace/skills/linguistics/SKILL.md +429 -0
- package/marketplace/skills/lint-overlay/SKILL.md +76 -0
- package/marketplace/skills/mental-models/SKILL.md +126 -0
- package/marketplace/skills/merge-queue/SKILL.md +94 -0
- package/marketplace/skills/methodology/SKILL.md +317 -0
- package/marketplace/skills/microcopy/SKILL.md +232 -0
- package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
- package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
- package/marketplace/skills/mutation-testing/SKILL.md +112 -0
- package/marketplace/skills/naming-conventions/SKILL.md +112 -0
- package/marketplace/skills/observability-modeling/SKILL.md +59 -0
- package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
- package/marketplace/skills/owasp-security/SKILL.md +153 -0
- package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
- package/marketplace/skills/performance-budgets/SKILL.md +185 -0
- package/marketplace/skills/performance-engineering/SKILL.md +58 -0
- package/marketplace/skills/performance-testing/SKILL.md +125 -0
- package/marketplace/skills/printify/SKILL.md +42 -0
- package/marketplace/skills/prioritization/SKILL.md +118 -0
- package/marketplace/skills/problem-framing/SKILL.md +41 -0
- package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
- package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
- package/marketplace/skills/prompt-craft/SKILL.md +134 -0
- package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
- package/marketplace/skills/property-based-testing/SKILL.md +100 -0
- package/marketplace/skills/prototyping/SKILL.md +43 -0
- package/marketplace/skills/query-optimization/SKILL.md +144 -0
- package/marketplace/skills/real-time-updates/SKILL.md +324 -0
- package/marketplace/skills/ref-patterns/SKILL.md +284 -0
- package/marketplace/skills/refactor/SKILL.md +65 -0
- package/marketplace/skills/rendering-models/SKILL.md +142 -0
- package/marketplace/skills/replication-patterns/SKILL.md +110 -0
- package/marketplace/skills/research-synthesis/SKILL.md +41 -0
- package/marketplace/skills/route-handler-design/SKILL.md +347 -0
- package/marketplace/skills/schema-evolution/SKILL.md +140 -0
- package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
- package/marketplace/skills/semantic-center/SKILL.md +194 -0
- package/marketplace/skills/semantic-relations/SKILL.md +250 -0
- package/marketplace/skills/semantics/SKILL.md +366 -0
- package/marketplace/skills/semiotics/SKILL.md +230 -0
- package/marketplace/skills/seo-strategy/SKILL.md +260 -0
- package/marketplace/skills/server-actions-design/SKILL.md +243 -0
- package/marketplace/skills/server-components-design/SKILL.md +190 -0
- package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
- package/marketplace/skills/shopify/SKILL.md +42 -0
- package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
- package/marketplace/skills/skill-router/SKILL.md +71 -0
- package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
- package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
- package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
- package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
- package/marketplace/skills/state-management/SKILL.md +134 -0
- package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
- package/marketplace/skills/summarization/SKILL.md +156 -0
- package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
- package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
- package/marketplace/skills/task-analysis/SKILL.md +201 -0
- package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
- package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
- package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
- package/marketplace/skills/test-driven-development/SKILL.md +96 -0
- package/marketplace/skills/testing-strategy/SKILL.md +67 -0
- package/marketplace/skills/theme-system-design/SKILL.md +43 -0
- package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
- package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
- package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
- package/marketplace/skills/type-safety/SKILL.md +177 -0
- package/marketplace/skills/typography-system/SKILL.md +43 -0
- package/marketplace/skills/usability-testing/SKILL.md +43 -0
- package/marketplace/skills/user-research/SKILL.md +43 -0
- package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
- package/marketplace/skills/version-control/SKILL.md +233 -0
- package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
- package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
- package/marketplace/skills/webhook-integration/SKILL.md +331 -0
- package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
- package/package.json +67 -0
- package/schemas/manifest.schema.json +811 -0
- package/schemas/manifest.v2.schema.json +164 -0
- package/schemas/manifest.v3.schema.json +758 -0
- package/schemas/manifest.v4.schema.json +755 -0
- package/schemas/manifest.v5.schema.json +755 -0
- package/schemas/manifest.v6.schema.json +811 -0
- package/schemas/skill.context.jsonld +279 -0
- package/schemas/skill.schema.json +919 -0
- package/schemas/skill.v2.schema.json +201 -0
- package/schemas/skill.v3.schema.json +827 -0
- package/schemas/skill.v4.schema.json +822 -0
- package/schemas/skill.v5.schema.json +830 -0
- package/schemas/skill.v6.schema.json +946 -0
- package/schemas/vocabulary/keywords.json +180 -0
- package/schemas/vocabulary/workspace_tags.json +23 -0
- package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
- package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
- package/scripts/__tests__/test-export-parser-drift.js +149 -0
- package/scripts/__tests__/test-marketplace-export.js +114 -0
- package/scripts/__tests__/test-router-paths.js +82 -0
- package/scripts/__tests__/test-stability-promotion.js +244 -0
- package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
- package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
- package/scripts/backfill-schema-version.js +198 -0
- package/scripts/build-field-reference.js +160 -0
- package/scripts/build-retrieval-baseline.js +511 -0
- package/scripts/check-markdown-links.js +211 -0
- package/scripts/check-protocol-consistency.js +979 -0
- package/scripts/export-marketplace-skills.js +610 -0
- package/scripts/export-skill.js +374 -0
- package/scripts/generate-manifest.js +787 -0
- package/scripts/lib/alias-contract.js +83 -0
- package/scripts/lib/audit-prompt-builder.js +771 -0
- package/scripts/lib/mock-grader.js +134 -0
- package/scripts/lib/parse-frontmatter.js +429 -0
- package/scripts/lib/roots.js +119 -0
- package/scripts/lint/check-archetype-sections.js +185 -0
- package/scripts/lint/check-category-enum.js +83 -0
- package/scripts/lint/check-routing-eval.js +146 -0
- package/scripts/lint/check-routing-quality.js +211 -0
- package/scripts/lint/check-stability-promotion.js +220 -0
- package/scripts/lint/format-code-frame.js +206 -0
- package/scripts/marketplace-install.js +125 -0
- package/scripts/migrate-category-to-enum.js +169 -0
- package/scripts/migrate-skill-v2-to-v3.js +424 -0
- package/scripts/migrate-skill-v3-to-v4.js +200 -0
- package/scripts/migrate-skill-v5-to-v6.js +304 -0
- package/scripts/restructure-by-category.js +85 -0
- package/scripts/seed-publication-classification.js +282 -0
- package/scripts/skill-audit.js +893 -0
- package/scripts/skill-graph-drift.js +483 -0
- package/scripts/skill-graph-route.js +766 -0
- package/scripts/skill-graph-routing-eval.js +393 -0
- package/scripts/skill-lint.js +1317 -0
- package/scripts/skill-overlap.js +213 -0
- package/scripts/verify-skill-md-export.js +201 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-boundary
|
|
3
|
+
description: "Use when designing or reviewing React error boundaries: what an error boundary catches (rendering errors, lifecycle errors, constructor errors) and what it does not (event handler errors, async errors, SSR errors, errors in the boundary itself), why React still requires class components for error boundaries, how to place boundaries by granularity (page / feature / leaf), how error boundaries pair with Suspense, the reset-and-recover pattern (resetKeys, error.reset), the Next.js error.tsx route-segment convention, and how to integrate boundaries with error reporting (Sentry, observability). Covers React 18+ and Next.js App Router. Do NOT use for Suspense boundary placement (use suspense-patterns), for general error-handling discipline (try/catch in async code, validation errors), for backend error contracts (use api-design), or for observability infrastructure (use error-tracking)."
|
|
4
|
+
license: MIT
|
|
5
|
+
allowed-tools: Read Grep
|
|
6
|
+
metadata:
|
|
7
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/frontend\",\"scope\":\"reference\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-16\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-16\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"comprehension_state\":\"present\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"React Error Boundary\\\\\\\",\\\\\\\"componentDidCatch\\\\\\\",\\\\\\\"getDerivedStateFromError\\\\\\\",\\\\\\\"react-error-boundary library\\\\\\\",\\\\\\\"Next.js error.tsx\\\\\\\",\\\\\\\"global-error.tsx\\\\\\\",\\\\\\\"error boundary granularity\\\\\\\",\\\\\\\"resetKeys\\\\\\\",\\\\\\\"error boundary with Suspense\\\\\\\",\\\\\\\"caught render error\\\\\\\",\\\\\\\"uncaught event handler error\\\\\\\",\\\\\\\"catch errors in React component tree\\\\\\\",\\\\\\\"show fallback UI when component crashes\\\\\\\",\\\\\\\"where to place error boundaries\\\\\\\",\\\\\\\"React component crash recovery\\\\\\\",\\\\\\\"error.tsx route segment fallback\\\\\\\"]\",\"triggers\":\"[\\\\\\\"my error boundary isn't catching errors\\\\\\\",\\\\\\\"do I need an error boundary here\\\\\\\",\\\\\\\"why does the whole page crash on one component error\\\\\\\",\\\\\\\"how do I recover from a caught error\\\\\\\",\\\\\\\"error.tsx vs global-error.tsx\\\\\\\",\\\\\\\"why doesn't this catch async errors\\\\\\\"]\",\"examples\":\"[\\\\\\\"design a route-segment error.tsx for a dashboard so one failing widget doesn't blank the whole page\\\\\\\",\\\\\\\"diagnose why a click-handler crash bypasses the error boundary and propagates to window.onerror\\\\\\\",\\\\\\\"pair an error boundary with a Suspense boundary so failed fetches show error UI while successful ones stream in\\\\\\\",\\\\\\\"integrate an error boundary with Sentry so caught errors are reported with the component tree context\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"design where to place Suspense fallback boundaries (use suspense-patterns)\\\\\\\",\\\\\\\"design the API response contract for a 500 error (use api-design)\\\\\\\",\\\\\\\"set up Sentry SDK initialization (use error-tracking)\\\\\\\",\\\\\\\"handle a Promise rejection in an event handler (use code-review for the local try/catch pattern)\\\\\\\",\\\\\\\"configure the Sentry, Datadog, or other error-tracking SDK and dashboards (use error-tracking)\\\\\\\"]\",\"relations\":\"{\\\\\\\"related\\\\\\\":[\\\\\\\"suspense-patterns\\\\\\\",\\\\\\\"hooks-patterns\\\\\\\",\\\\\\\"error-tracking\\\\\\\",\\\\\\\"server-components-design\\\\\\\"],\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"suspense-patterns\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"suspense-patterns owns the loading-state boundary mechanism; error-boundary owns the failure-state boundary mechanism. They pair in the canonical ErrorBoundary→Suspense→Component nesting but are distinct primitives that catch different signals (thrown Error vs thrown Promise).\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"hooks-patterns\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"hooks-patterns covers component-internal logic discipline; error-boundary is a tree-level mechanism that handles failures from anywhere in its descendant subtree. Class-component requirement of error boundaries is the one place hooks discipline does not apply.\\\\\\\"}],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"code-review\\\\\\\",\\\\\\\"error-tracking\\\\\\\"]}\",\"mental_model\":\"|\",\"purpose\":\"|\",\"boundary\":\"|\",\"analogy\":\"An error boundary is to React's component tree what a circuit breaker is to a building's electrical system — when a fault occurs in one circuit, the breaker for THAT circuit trips, isolating the fault so the rest of the building keeps running, while sub-panels still healthy on other circuits keep lights and outlets working. A single master breaker would protect against fault propagation but at the cost of darkening everything; the right granularity is one breaker per useful zone.\",\"misconception\":\"|\",\"concept\":\"{\\\\\\\"definition\\\\\\\":\\\\\\\"An error boundary is a React component that catches JavaScript errors thrown anywhere in its descendant tree during rendering, in lifecycle methods, and in constructors — and renders a fallback UI instead of unmounting the broken subtree to the root. As of React 19 it must still be implemented as a class component using getDerivedStateFromError and/or componentDidCatch; function components cannot themselves be error boundaries (they can render one and provide its fallback). React's design choice: failure in a subtree should not require crashing the whole tree, but the boundary itself must be a stable component above the failure point.\\\\\\\",\\\\\\\"mental_model\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"purpose\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"boundary\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"taxonomy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"analogy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"misconception\\\\\\\":\\\\\\\"|\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/error-boundary/SKILL.md\"}"
|
|
8
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
9
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
10
|
+
skill_graph_project: Skill Graph
|
|
11
|
+
skill_graph_canonical_skill: skills/error-boundary/SKILL.md
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Error Boundary
|
|
15
|
+
|
|
16
|
+
## Coverage
|
|
17
|
+
|
|
18
|
+
The discipline of placing and configuring React error boundaries: precisely what the boundary catches (rendering errors, lifecycle method errors, constructor errors) and what it provably cannot catch (event handler errors, async/setTimeout/Promise errors, server-side rendering errors before React 18.5, errors thrown by the boundary itself), why React 19 still requires class-component implementation, the boundary-placement granularity tradeoff (page / feature / leaf), the canonical Suspense+ErrorBoundary nesting and why their order matters, the reset-and-recover pattern via `resetKeys` / `FallbackComponent`, the Next.js App Router conventions (`error.tsx`, `global-error.tsx`), and how to wire boundaries to external error reporting so caught errors do not become silent UI degradation.
|
|
19
|
+
|
|
20
|
+
## Philosophy
|
|
21
|
+
|
|
22
|
+
Before React 16, an error thrown during render had nowhere to go. React's reconciler would catch it, log it, and continue trying to render — usually producing a corrupted or blank tree. The component model assumed that render functions don't throw, and the runtime had no recovery primitive when they did. The result was either total app crash or partial blank screens with no signal that anything had broken.
|
|
23
|
+
|
|
24
|
+
Error boundaries gave React the recovery primitive it had been missing. A class component that implements `getDerivedStateFromError` and/or `componentDidCatch` catches throws from anywhere in its descendant subtree during rendering, lifecycle methods, and constructors. When a throw occurs, React unmounts the broken subtree and renders the boundary's fallback in its place. The rest of the tree, outside the boundary, keeps running. A bug in one widget no longer means a blank page; it means that widget shows its fallback and everything else is fine.
|
|
25
|
+
|
|
26
|
+
The discipline is to **place boundaries by failure-blast-radius**. A boundary near the page root catches everything but replaces the entire page on any error. A boundary around a single widget catches only that widget's errors but lets the rest of the page keep working. The right granularity is the smallest scope where the fallback UI is still meaningful: a chart's error fallback ("Unable to load chart, retry?") is useful per-chart; a global "Something went wrong" replacing the whole app is useful only as a last resort.
|
|
27
|
+
|
|
28
|
+
The boundary's most important second-order property is **honesty**. A caught error is still an error. A boundary that swallows the throw without reporting externally degrades the UI silently — users see "Something went wrong," developers see nothing. The discipline is to pair every boundary with a report-to-Sentry (or equivalent) call, so the fallback is the user-facing recovery *and* the developer-facing alarm.
|
|
29
|
+
|
|
30
|
+
## What an Error Boundary Catches — and Doesn't
|
|
31
|
+
|
|
32
|
+
| Source of error | Caught by boundary? |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Throw during a component's render | Yes |
|
|
35
|
+
| Throw in `componentDidMount` / `componentDidUpdate` | Yes |
|
|
36
|
+
| Throw in a class constructor | Yes |
|
|
37
|
+
| Throw during hook execution (useState initializer, useEffect's render-phase setup) | Yes |
|
|
38
|
+
| Throw in a `useEffect` callback body | **No** — runs after commit, outside render phase |
|
|
39
|
+
| Throw in an event handler (`onClick`, `onChange`) | **No** — runs imperatively, outside render |
|
|
40
|
+
| Unhandled Promise rejection (async function thrown into a click handler) | **No** — async errors propagate to `window.onunhandledrejection` |
|
|
41
|
+
| `setTimeout` / `setInterval` callback throw | **No** — escapes React's call stack |
|
|
42
|
+
| Server-side rendering error (`renderToString` throw before React 18.5) | Partially — handled via `onError` in modern streaming APIs |
|
|
43
|
+
| Error thrown by the boundary itself | **No** — propagates to the next ancestor boundary |
|
|
44
|
+
|
|
45
|
+
The pattern: error boundaries catch errors thrown **inside React's call stack during render or lifecycle**. Errors that happen outside that stack — event handlers, async callbacks, promise rejections — must be handled with a local `try/catch` and then surfaced into React via `setState` (so the boundary catches the re-render with the error state).
|
|
46
|
+
|
|
47
|
+
Canonical async-error pattern:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
function MyComponent() {
|
|
51
|
+
const [error, setError] = useState<Error | null>(null)
|
|
52
|
+
if (error) throw error // ← rethrow into render, so the boundary catches it
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<button onClick={async () => {
|
|
56
|
+
try {
|
|
57
|
+
await doRiskyThing()
|
|
58
|
+
} catch (e) {
|
|
59
|
+
setError(e as Error) // ← surface async error into state
|
|
60
|
+
}
|
|
61
|
+
}}>
|
|
62
|
+
Click
|
|
63
|
+
</button>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This pattern is verbose but explicit. The boundary catches all *rendering* errors automatically; async errors must be opted-in by re-throwing during render.
|
|
69
|
+
|
|
70
|
+
## React Still Requires a Class Component
|
|
71
|
+
|
|
72
|
+
Despite the rest of React moving to function components and hooks, error boundaries must be class components as of React 19. The reason: the boundary's defining methods are `static getDerivedStateFromError` and `componentDidCatch`, which have no hooks equivalent. The React team has discussed `useErrorBoundary` but it has not shipped.
|
|
73
|
+
|
|
74
|
+
Minimal class boundary:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Component, ReactNode } from 'react'
|
|
78
|
+
|
|
79
|
+
class ErrorBoundary extends Component<
|
|
80
|
+
{ fallback: ReactNode; children: ReactNode; onError?: (e: Error) => void },
|
|
81
|
+
{ hasError: boolean }
|
|
82
|
+
> {
|
|
83
|
+
state = { hasError: false }
|
|
84
|
+
|
|
85
|
+
static getDerivedStateFromError() {
|
|
86
|
+
return { hasError: true }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
|
90
|
+
this.props.onError?.(error)
|
|
91
|
+
// Report to Sentry/Datadog/etc here
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
render() {
|
|
95
|
+
return this.state.hasError ? this.props.fallback : this.props.children
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
In practice, use [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary) by Brian Vaughn / Kent C. Dodds — it provides `<ErrorBoundary>` with reset support, `FallbackComponent`, `resetKeys`, and `onError` / `onReset` props out of the box. Implementing the class yourself is fine, but `react-error-boundary` covers the cases (recovery, key-based reset) you'll eventually want.
|
|
101
|
+
|
|
102
|
+
## Granularity — Where to Place the Boundary
|
|
103
|
+
|
|
104
|
+
Three placement strategies, each correct in different contexts:
|
|
105
|
+
|
|
106
|
+
**1. App-root boundary.** One boundary at the top, catches everything else's failures. Renders a "Something went wrong" generic page.
|
|
107
|
+
|
|
108
|
+
- **Use as**: backstop only. The last line of defense if every finer-grained boundary fails or doesn't exist.
|
|
109
|
+
- **Don't rely on alone**: when this catches an error, the user has lost everything.
|
|
110
|
+
|
|
111
|
+
**2. Route-segment boundary** (Next.js `error.tsx`). One boundary per page or layout segment.
|
|
112
|
+
|
|
113
|
+
- **Use when**: each route is a meaningful unit; failure in one route shouldn't blank the whole app shell.
|
|
114
|
+
- **Next.js sugar**: an `app/dashboard/error.tsx` file is automatically a route-scoped boundary around `app/dashboard/page.tsx`. A `global-error.tsx` is the app-root backstop.
|
|
115
|
+
|
|
116
|
+
**3. Feature/widget boundary.** A boundary around each independent feature or data-loading component.
|
|
117
|
+
|
|
118
|
+
- **Use when**: many features render in parallel and failure in one shouldn't take down siblings (dashboard widgets, list-of-items where one item's data is malformed).
|
|
119
|
+
- **Cost**: more fallback UIs to design; potential "many error messages" surface.
|
|
120
|
+
|
|
121
|
+
The right approach is layered: app-root + route-segment + feature, with each catching what its layer scopes. A leaf widget's error hits the feature boundary; an unhandled error in the feature boundary hits the route boundary; a catastrophic error in the route boundary hits the app-root boundary.
|
|
122
|
+
|
|
123
|
+
## Pairing with Suspense
|
|
124
|
+
|
|
125
|
+
Error boundaries catch *thrown Errors*. Suspense boundaries catch *thrown Promises*. They are distinct mechanisms and must be different components, but they almost always nest together:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
<ErrorBoundary fallback={<ErrorUI />}>
|
|
129
|
+
<Suspense fallback={<LoadingUI />}>
|
|
130
|
+
<DataLoadingComponent />
|
|
131
|
+
</Suspense>
|
|
132
|
+
</ErrorBoundary>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Order matters:
|
|
136
|
+
|
|
137
|
+
- **ErrorBoundary outside Suspense**: a failed fetch replaces the entire group (loading state too) with the error UI. Most applications want this — a single failure surface.
|
|
138
|
+
- **Suspense outside ErrorBoundary**: the Suspense fallback shows during loading; if the fetch then fails, the inner error UI takes its place inside the Suspense slot. Useful when the loading state should remain visible for everything else and only a small portion swaps to error.
|
|
139
|
+
|
|
140
|
+
`react-error-boundary` provides `onReset` that pairs with Suspense's transition APIs to retry a failed fetch:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<ErrorBoundary
|
|
144
|
+
fallbackRender={({ resetErrorBoundary }) => (
|
|
145
|
+
<ErrorUI onRetry={resetErrorBoundary} />
|
|
146
|
+
)}
|
|
147
|
+
resetKeys={[queryId]}
|
|
148
|
+
>
|
|
149
|
+
<Suspense fallback={<Spinner />}>
|
|
150
|
+
<QueryResult queryId={queryId} />
|
|
151
|
+
</Suspense>
|
|
152
|
+
</ErrorBoundary>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
When `queryId` changes (e.g., user clicks a different item), the boundary auto-resets and tries again. When the user clicks Retry, `resetErrorBoundary` clears the error state and the Suspense boundary re-tries its async render.
|
|
156
|
+
|
|
157
|
+
See `suspense-patterns` for the full Suspense discipline; this skill focuses on the error half of the pair.
|
|
158
|
+
|
|
159
|
+
## Next.js App Router Conventions
|
|
160
|
+
|
|
161
|
+
The App Router gives error boundaries first-class file conventions:
|
|
162
|
+
|
|
163
|
+
| File | Scope | What it catches |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| `app/<segment>/error.tsx` | Route segment | Errors during rendering of that segment's `page.tsx` or any descendant Server/Client component; receives `error` and `reset` props |
|
|
166
|
+
| `app/global-error.tsx` | Root layout fallback | Errors that escape every `error.tsx` — including errors in the root layout itself |
|
|
167
|
+
| `app/<segment>/not-found.tsx` | Route segment | `notFound()` calls — special case of error, distinct UI |
|
|
168
|
+
|
|
169
|
+
`error.tsx` must be a Client Component (it uses state for the reset action) and is implicitly wrapped in a route-segment-level error boundary. The convention pairs with `loading.tsx` (Suspense fallback) and produces the canonical "fast page with skeleton, then content with error-recovery if something breaks" pattern without manual boundary wiring.
|
|
170
|
+
|
|
171
|
+
Important constraint: `error.tsx` only catches errors from *its descendant tree*, not from its sibling layout. An error in `app/layout.tsx` escapes the page's `error.tsx` and only `global-error.tsx` can catch it.
|
|
172
|
+
|
|
173
|
+
## Reporting Caught Errors
|
|
174
|
+
|
|
175
|
+
A caught error is still an error. Wire every boundary to external reporting:
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<ErrorBoundary
|
|
179
|
+
FallbackComponent={ErrorFallback}
|
|
180
|
+
onError={(error, info) => {
|
|
181
|
+
Sentry.captureException(error, { contexts: { react: { componentStack: info.componentStack } } })
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<App />
|
|
185
|
+
</ErrorBoundary>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`info.componentStack` is React's stack trace of which components were rendering when the error occurred — crucial for diagnosis, distinct from the JavaScript stack trace.
|
|
189
|
+
|
|
190
|
+
Sentry's `@sentry/react` provides a built-in `<Sentry.ErrorBoundary>` that handles reporting automatically. The discipline applies regardless of tool: caught errors must surface somewhere developers see them. A boundary without reporting is a UX feature with no signal-back loop. See `error-tracking` for the broader observability discipline this skill plugs into.
|
|
191
|
+
|
|
192
|
+
## Common Anti-Patterns
|
|
193
|
+
|
|
194
|
+
| Anti-pattern | Why it's wrong | Fix |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| Single app-root boundary, no finer scopes | Any error blanks the whole app | Layer boundaries: app-root + route-segment + feature |
|
|
197
|
+
| Boundary that swallows the error without reporting | Silent UI degradation | Wire `onError` / `componentDidCatch` to Sentry or equivalent |
|
|
198
|
+
| Trying to catch event-handler errors with a boundary | Boundary doesn't catch async/imperative errors | Local `try/catch` in the handler, surface to state via `setError`, re-throw during render |
|
|
199
|
+
| Boundary at the same level as the failing component | A throw during the boundary's own render escapes the boundary | Boundary must be *above* the failing component in the tree |
|
|
200
|
+
| `error.tsx` placed in the wrong route segment | Errors in `app/layout.tsx` aren't caught by `app/page/error.tsx` | Place `error.tsx` at the segment that owns the failing render; use `global-error.tsx` for the root layout |
|
|
201
|
+
| ErrorBoundary inside Suspense (when goal is "failure replaces loading state") | Loading state lingers after fetch fails | Put ErrorBoundary outside Suspense for the common case |
|
|
202
|
+
| Function-component error boundary attempt | Hooks API has no `useErrorBoundary` (as of React 19) | Use a class component or the `react-error-boundary` library |
|
|
203
|
+
| Reset that doesn't actually retry the failed work | User clicks "Retry" but the same stale error state persists | Use `resetKeys` tied to the inputs that should retry, or `resetErrorBoundary` from `react-error-boundary` |
|
|
204
|
+
|
|
205
|
+
## Verification
|
|
206
|
+
|
|
207
|
+
After applying this skill, verify:
|
|
208
|
+
|
|
209
|
+
- [ ] Every route segment with risk of render-time failure has an `error.tsx` (or equivalent boundary).
|
|
210
|
+
- [ ] Every boundary has an `onError` / `componentDidCatch` that reports to external observability.
|
|
211
|
+
- [ ] Event-handler errors and async errors are caught locally and surfaced via state, not assumed to hit the boundary directly.
|
|
212
|
+
- [ ] Error boundary placement matches failure-blast-radius: app-root for catastrophic, route-segment for navigational, feature-level for partial.
|
|
213
|
+
- [ ] When pairing with Suspense, the nesting order matches the desired failure-mode UI (ErrorBoundary outside Suspense for the common case).
|
|
214
|
+
- [ ] Reset path actually retries the failed work — `resetKeys` are tied to inputs that change on retry.
|
|
215
|
+
- [ ] `global-error.tsx` exists as the app-root backstop.
|
|
216
|
+
- [ ] The class-component requirement is acknowledged — no failed attempt to write a function-component boundary without a library.
|
|
217
|
+
|
|
218
|
+
## Grounding Sources
|
|
219
|
+
|
|
220
|
+
- React docs — [Catching rendering errors with an error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The official reference for `getDerivedStateFromError` and `componentDidCatch`.
|
|
221
|
+
- React legacy docs — [Error Boundaries](https://legacy.reactjs.org/docs/error-boundaries.html). The original explanation from React 16; still the clearest statement of what boundaries do and don't catch.
|
|
222
|
+
- Vaughn, B. / Dodds, K. C. — [`react-error-boundary`](https://github.com/bvaughn/react-error-boundary). The de facto library; reset semantics and `FallbackComponent` API.
|
|
223
|
+
- Next.js docs — [`error.tsx`](https://nextjs.org/docs/app/api-reference/file-conventions/error) and [`global-error.tsx`](https://nextjs.org/docs/app/api-reference/file-conventions/error#global-errorjs). Route-segment boundary conventions.
|
|
224
|
+
- Sentry — [React Error Boundary integration](https://docs.sentry.io/platforms/javascript/guides/react/features/error-boundary/). The observability-pairing pattern.
|
|
225
|
+
- Dodds, K. C. — [Use react-error-boundary to handle errors in React](https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react). Composition patterns and reset strategies.
|
|
226
|
+
|
|
227
|
+
## Do NOT Use When
|
|
228
|
+
|
|
229
|
+
| Instead of this skill | Use | Why |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| Suspense boundary placement (loading states, streaming) | `suspense-patterns` | suspense-patterns owns the loading-state mechanism; this skill owns the failure-state mechanism. They pair but are distinct primitives. |
|
|
232
|
+
| Setting up the observability SDK (Sentry init, breadcrumbs, sampling) | `error-tracking` | error-tracking owns the SDK and infrastructure; this skill owns the in-tree React boundary that feeds caught errors into the SDK. |
|
|
233
|
+
| Try/catch discipline in async code (event handlers, promises, await) | `code-review` (or general async error patterns) | Async errors are outside React's call stack — they need local handling first, then re-throwing into render to reach the boundary. |
|
|
234
|
+
| Backend error response design (HTTP status codes, error envelope format) | `api-design` | api-design owns the wire format for backend errors; this skill handles client-side errors after the response. |
|
|
235
|
+
| Server Component error handling (server-side render failure) | `server-components-design` | Server-side rendering errors are caught by `error.tsx` files at the route segment level — a Next.js convention, technically distinct from React's render-phase error boundary. |
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-tracking
|
|
3
|
+
description: "Use when designing or extending an application exception-reporting pipeline: error boundary placement, tracker SDK wrappers, sanitized reporting calls, environment gating, user context without PII leaks, breadcrumbs, and verification that each layer reports correctly. Covers component, route, global, and manual capture surfaces plus central `reportError`/`reportMessage` patterns. Do NOT use for the visual error UX shown to users (use `a11y` and interaction skills), chasing one captured error (use `debugging`), or broad privacy and retention policy (use `owasp-security`)."
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: "Tracker-agnostic. The patterns target any exception-reporting SDK with a `captureException` / `captureMessage` / `addBreadcrumb` shape — Sentry, Rollbar, Bugsnag, Honeybadger, Datadog Errors, Application Insights. Examples are framed in React + Next.js because that is the most common surface; analogous primitives exist in Vue (`errorCaptured`), Svelte (error stores), Remix (`ErrorBoundary`), Nuxt (`error.vue`), and any framework with framework-level error hooks."
|
|
6
|
+
allowed-tools: Read Grep Bash Edit
|
|
7
|
+
metadata:
|
|
8
|
+
metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/observability\",\"scope\":\"portable\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-06\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-06\\\\\\\"}\",\"eval_artifacts\":\"present\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"error tracking\\\\\\\",\\\\\\\"exception reporting\\\\\\\",\\\\\\\"error reporting\\\\\\\",\\\\\\\"error boundary\\\\\\\",\\\\\\\"React ErrorBoundary\\\\\\\",\\\\\\\"route error boundary\\\\\\\",\\\\\\\"global error boundary\\\\\\\",\\\\\\\"error tracker SDK\\\\\\\",\\\\\\\"Sentry integration\\\\\\\",\\\\\\\"captureException wrapper\\\\\\\",\\\\\\\"captureMessage wrapper\\\\\\\",\\\\\\\"addBreadcrumb wrapper\\\\\\\",\\\\\\\"set user context error\\\\\\\",\\\\\\\"PII sanitization error payload\\\\\\\",\\\\\\\"environment-aware error reporting\\\\\\\",\\\\\\\"dev logger prod tracker\\\\\\\",\\\\\\\"error digest deduplication\\\\\\\",\\\\\\\"catastrophic failure capture\\\\\\\",\\\\\\\"reportError wrapper\\\\\\\",\\\\\\\"error tracking provider\\\\\\\"]\",\"examples\":\"[\\\\\\\"set up exception reporting for a new React + Next.js application\\\\\\\",\\\\\\\"add a route-level error boundary that recovers gracefully and still reports\\\\\\\",\\\\\\\"the error tracker is showing customer emails in event payloads — fix the PII leak\\\\\\\",\\\\\\\"wrap captureException in a centralized reporter that adds environment gating\\\\\\\",\\\\\\\"audit the error pipeline — confirm every layer eventually reaches the tracker\\\\\\\",\\\\\\\"decide where error boundaries should live: component, section, route, or app-global\\\\\\\",\\\\\\\"implement PII sanitization for error payloads before they hit the tracker SDK\\\\\\\",\\\\\\\"set user context (internal id, org id, role) on errors without sending email or name\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"design accessible error-message copy and recovery UI for the 404 page\\\\\\\",\\\\\\\"the boundary fired but the tracker shows no event — root-cause it\\\\\\\",\\\\\\\"explain our error-tracking architecture in the contributor docs\\\\\\\",\\\\\\\"review this AI-generated error handler for correctness\\\\\\\",\\\\\\\"decide if the new error path needs an integration regression test\\\\\\\",\\\\\\\"design our overall PII storage and retention policy across the system\\\\\\\",\\\\\\\"refactor the error-helper module for clarity\\\\\\\"]\",\"relations\":\"{\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"debugging\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"debugging chases an observed failure already in the tracker; error-tracking designs the pipeline that captures and routes failures in the first place\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"owasp-security\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"owasp-security owns the cross-cutting PII and credential-handling policy; error-tracking owns the request-time sanitization that error payloads pass through before leaving the application\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"a11y\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"a11y owns the user-visible error UX (message copy, focus management, screen-reader announcements); error-tracking owns the engineering pipeline behind the error boundary\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"refactor\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"refactor reorganizes existing code while preserving behavior; error-tracking changes the *behavior* of the error pipeline (where it reports, what it sanitizes, how layers compose)\\\\\\\"}],\\\\\\\"related\\\\\\\":[\\\\\\\"debugging\\\\\\\",\\\\\\\"owasp-security\\\\\\\",\\\\\\\"a11y\\\\\\\",\\\\\\\"code-review\\\\\\\",\\\\\\\"testing-strategy\\\\\\\"],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"code-review\\\\\\\",\\\\\\\"testing-strategy\\\\\\\"]}\",\"portability\":\"{\\\\\\\"readiness\\\\\\\":\\\\\\\"scripted\\\\\\\",\\\\\\\"targets\\\\\\\":[\\\\\\\"skill-md\\\\\\\"]}\",\"lifecycle\":\"{\\\\\\\"stale_after_days\\\\\\\":90,\\\\\\\"review_cadence\\\\\\\":\\\\\\\"quarterly\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/error-tracking/SKILL.md\",\"skill_graph_export_description\":\"shortened for Agent Skills 1024-character description limit; canonical source keeps the full routing contract\",\"skill_graph_canonical_description_length\":\"1045\"}"
|
|
9
|
+
skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
|
|
10
|
+
skill_graph_protocol: Skill Metadata Protocol v4
|
|
11
|
+
skill_graph_project: Skill Graph
|
|
12
|
+
skill_graph_canonical_skill: skills/error-tracking/SKILL.md
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Error Tracking
|
|
16
|
+
|
|
17
|
+
## Coverage
|
|
18
|
+
|
|
19
|
+
- The four error-capture surfaces every application has, whether by design or accident: component-level boundaries, route-level fallbacks, application-global crash handler, and manual reporting from non-UI code paths
|
|
20
|
+
- The centralized-wrapper pattern: a thin module (`reportError`, `reportMessage`, `addBreadcrumb`, `setUser`, `clearUser`, `isErrorTrackingEnabled`) that sits between application code and the tracker SDK
|
|
21
|
+
- PII sanitization before any external send: a `sanitizePII()` pass over every payload, the rule that internal IDs are sent and email / name / phone are not, and the verification that wrapper-time sanitization is the actual scrubber (not the tracker SDK's `beforeSend`, which is a backstop at best)
|
|
22
|
+
- Environment-aware gating: dev mode emits to a local logger, production emits to the tracker; the gate is a single function so testing is trivial
|
|
23
|
+
- User context: setting and clearing `userId`, `orgId`, `role` (or equivalent) at session boundaries; the object signature versus positional-arg trap
|
|
24
|
+
- Error breadcrumbs: how to add navigation, network, and state context to events without leaking sensitive data
|
|
25
|
+
- Diagnostic discipline: the question *"does this layer actually report, or does it only log?"* — most route-level fallbacks log locally and never reach the tracker unless wired up explicitly
|
|
26
|
+
- Catastrophic-failure path: app-global handler bypasses the wrapper and goes straight to the SDK because the wrapper itself may have failed
|
|
27
|
+
|
|
28
|
+
## Philosophy
|
|
29
|
+
|
|
30
|
+
Most production applications develop an error-tracking architecture by accident, one try/catch at a time. The accumulated result is unprincipled: some errors reach the tracker with full PII, some are silently swallowed, some are double-reported (once by the boundary, once by the catch), and the dev-prod gating is per-call instead of central. The first time someone has to audit it — usually after a customer reports their email appearing in a third-party error dashboard — the cost of repair is enormous because every call site has to be revisited.
|
|
31
|
+
|
|
32
|
+
The discipline is to *centralize*. Application code never imports the tracker SDK directly; it imports a thin wrapper module. The wrapper owns three concerns: sanitization, environment gating, and signature stability. Sanitization runs on every payload before any external call. Environment gating decides whether the call goes to a local logger or to the tracker SDK. Signature stability means the wrapper's API does not change when the underlying tracker is swapped — application code is insulated from vendor decisions.
|
|
33
|
+
|
|
34
|
+
The second principle is *layering*. Errors arrive through multiple surfaces and each layer is responsible for a different recovery story. Component boundaries catch render-time and effect-time exceptions in a subtree and show a fallback UI. Route boundaries catch errors in a specific route and let other routes keep working. The application-global handler catches everything else and shows a "something went wrong" screen. Manual reporting handles errors caught in non-UI code (background jobs, server actions, async event handlers). Each layer must report — the question to verify on every change is *"does this layer's catch path actually call the wrapper, or does it only log locally?"* The default for many framework-provided fallbacks is the latter.
|
|
35
|
+
|
|
36
|
+
The third principle is *internal IDs only*. The tracker is operated by a third party. Even with a strict DPA, sending email addresses or names is a privacy escalation that has no justification — internal user and org IDs are sufficient to identify the affected account and let an engineer query application logs for the rest. PII appears in error payloads usually as a side effect of "let's just dump the request body" — `sanitizePII()` is the single chokepoint that catches this.
|
|
37
|
+
|
|
38
|
+
## The Four Error-Capture Surfaces
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Surface 1: Component-level boundary
|
|
42
|
+
React `ErrorBoundary` / Vue `errorCaptured` / Svelte error store
|
|
43
|
+
Catches render-time and effect-time exceptions in the subtree
|
|
44
|
+
Path: catch -> sanitize -> wrapper -> dev logger or tracker
|
|
45
|
+
|
|
46
|
+
Surface 2: Route-level boundary
|
|
47
|
+
Next.js `error.tsx` / Remix `ErrorBoundary` / Nuxt `error.vue`
|
|
48
|
+
Catches per-route errors and shows route-scoped recovery UI
|
|
49
|
+
Path: framework hook -> shared fallback component -> wrapper
|
|
50
|
+
(Many framework-provided fallbacks ONLY log locally; verify they call the wrapper)
|
|
51
|
+
|
|
52
|
+
Surface 3: Application-global crash handler
|
|
53
|
+
Next.js `global-error.tsx` / framework-equivalent root handler
|
|
54
|
+
Catches anything that escaped the route layer
|
|
55
|
+
Path: bypasses wrapper and calls tracker SDK directly
|
|
56
|
+
(The wrapper itself may have failed; rely only on the SDK at this layer)
|
|
57
|
+
|
|
58
|
+
Surface 4: Manual reporting (non-UI paths)
|
|
59
|
+
Background jobs, server actions, async handlers, queue workers
|
|
60
|
+
Path: try / catch -> wrapper.reportError(error, { tags, extra })
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When extending or auditing the architecture, walk all four surfaces. The most common gap is Surface 2 — a route fallback shipped that renders a recovery UI but never calls `reportError`, so the production tracker shows zero events for that route while users see "something went wrong."
|
|
64
|
+
|
|
65
|
+
## The Centralized Wrapper
|
|
66
|
+
|
|
67
|
+
The wrapper module is small. Six functions cover most needs:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// error-tracker.ts
|
|
71
|
+
|
|
72
|
+
import { tracker } from 'your-tracker-sdk';
|
|
73
|
+
import { sanitizePII } from './pii-sanitizer';
|
|
74
|
+
import { logger } from './logger';
|
|
75
|
+
|
|
76
|
+
const enabled = isProductionEnv() && hasTrackerDsn();
|
|
77
|
+
|
|
78
|
+
export function isErrorTrackingEnabled(): boolean {
|
|
79
|
+
return enabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function reportError(
|
|
83
|
+
error: Error | string,
|
|
84
|
+
context?: { tags?: Record<string, string>; extra?: unknown; level?: 'fatal' | 'error' | 'warning' }
|
|
85
|
+
): void {
|
|
86
|
+
const sanitized = context?.extra ? sanitizePII(context.extra) : undefined;
|
|
87
|
+
|
|
88
|
+
if (!enabled) {
|
|
89
|
+
logger.error(error, { ...context, extra: sanitized });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
tracker.captureException(error, {
|
|
94
|
+
tags: context?.tags,
|
|
95
|
+
contexts: sanitized ? { sanitized } : undefined,
|
|
96
|
+
level: context?.level ?? 'error',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function reportMessage(
|
|
101
|
+
message: string,
|
|
102
|
+
context?: { tags?: Record<string, string>; extra?: unknown; level?: 'info' | 'warning' | 'error' }
|
|
103
|
+
): void {
|
|
104
|
+
const sanitized = context?.extra ? sanitizePII(context.extra) : undefined;
|
|
105
|
+
|
|
106
|
+
if (!enabled) {
|
|
107
|
+
logger[context?.level ?? 'info'](message, { ...context, extra: sanitized });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
tracker.captureMessage(message, {
|
|
112
|
+
tags: context?.tags,
|
|
113
|
+
contexts: sanitized ? { sanitized } : undefined,
|
|
114
|
+
level: context?.level ?? 'info',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function setUser(user: { userId?: string; orgId?: string; role?: string } | null): void {
|
|
119
|
+
if (!enabled) return;
|
|
120
|
+
tracker.setUser(user);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function clearUser(): void {
|
|
124
|
+
if (!enabled) return;
|
|
125
|
+
tracker.setUser(null);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function addBreadcrumb(
|
|
129
|
+
category: string,
|
|
130
|
+
message: string,
|
|
131
|
+
data?: unknown,
|
|
132
|
+
level?: 'info' | 'warning' | 'error'
|
|
133
|
+
): void {
|
|
134
|
+
const sanitized = data ? sanitizePII(data) : undefined;
|
|
135
|
+
if (!enabled) {
|
|
136
|
+
logger.debug(`[breadcrumb:${category}] ${message}`, sanitized);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
tracker.addBreadcrumb({ category, message, data: sanitized, level: level ?? 'info' });
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Every application code path uses these functions; nothing imports the tracker SDK directly *except* the application-global crash handler (which is the one place where the wrapper itself may have crashed).
|
|
144
|
+
|
|
145
|
+
## PII Sanitization
|
|
146
|
+
|
|
147
|
+
`sanitizePII()` is the chokepoint. It is the *only* point at which payloads are scrubbed; the tracker SDK's `beforeSend` hook is a backstop, not the primary defence. Wrapping at the SDK level alone misses everything that flows through `breadcrumb` and `extra` outside the exception flow.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// pii-sanitizer.ts (sketch)
|
|
151
|
+
const PII_KEY_PATTERNS = [
|
|
152
|
+
/^email$/i, /^e_mail$/i,
|
|
153
|
+
/^phone$/i, /^phone_number$/i,
|
|
154
|
+
/^name$/i, /^first_name$/i, /^last_name$/i, /^full_name$/i,
|
|
155
|
+
/^address$/i, /^street$/i, /^postal_code$/i,
|
|
156
|
+
/^ssn$/i, /^tax_id$/i,
|
|
157
|
+
/token/i, /secret/i, /password/i, /api_key/i,
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
export function sanitizePII(value: unknown, depth = 0): unknown {
|
|
161
|
+
if (depth > 6) return '[max-depth]';
|
|
162
|
+
if (value === null || value === undefined) return value;
|
|
163
|
+
if (typeof value !== 'object') return value;
|
|
164
|
+
if (Array.isArray(value)) return value.map(v => sanitizePII(v, depth + 1));
|
|
165
|
+
|
|
166
|
+
const out: Record<string, unknown> = {};
|
|
167
|
+
for (const [k, v] of Object.entries(value)) {
|
|
168
|
+
if (PII_KEY_PATTERNS.some(re => re.test(k))) {
|
|
169
|
+
out[k] = '[redacted]';
|
|
170
|
+
} else {
|
|
171
|
+
out[k] = sanitizePII(v, depth + 1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The patterns and the recursion bound are workload-specific; tune to your domain. The non-negotiable property is that *every* path into the wrapper passes through `sanitizePII` — there is no "trusted" call site.
|
|
179
|
+
|
|
180
|
+
## User Context
|
|
181
|
+
|
|
182
|
+
User context is set at the moment a session is established and cleared on sign-out. The signature is an object, not positional arguments — positional args break silently when one of the optional fields shifts position.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// On sign-in or session restore
|
|
186
|
+
setUser({
|
|
187
|
+
userId: session.user.id,
|
|
188
|
+
orgId: session.user.orgId,
|
|
189
|
+
role: session.user.role,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// On sign-out
|
|
193
|
+
clearUser();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
What goes in: stable internal identifiers (UUIDs, role enums). What does *not* go in: email, full name, phone, IP address, anything that could identify a real person to a tracker operator who already has their own customer database.
|
|
197
|
+
|
|
198
|
+
For a multi-tenant application, the `orgId` is essential — it scopes the error to a specific customer organization, lets you query "show me all errors for tenant X," and makes incident response actually possible. Without `orgId`, every error event is anonymous and a tenant-affecting bug looks like a fleet-wide problem.
|
|
199
|
+
|
|
200
|
+
## Environment-Aware Gating
|
|
201
|
+
|
|
202
|
+
The gating decision lives in one place: `isErrorTrackingEnabled()`. Two conditions: production-equivalent environment *and* tracker DSN configured. If either is missing, every wrapper call falls through to the local logger.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
function isProductionEnv(): boolean {
|
|
206
|
+
return process.env.NODE_ENV === 'production';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function hasTrackerDsn(): boolean {
|
|
210
|
+
return Boolean(process.env.TRACKER_DSN ?? process.env.SENTRY_DSN ?? process.env.ROLLBAR_TOKEN);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The benefit is that *every test* exercises the same wrapper and the same sanitization path as production, except the final hop hits a logger instead of a tracker. Tests can assert on logger output to verify what *would* have been sent. Without this gating, tests either use a mock tracker (which drifts from production behavior) or skip the path entirely (which means PII leaks slip through CI).
|
|
215
|
+
|
|
216
|
+
## Diagnostic Discipline
|
|
217
|
+
|
|
218
|
+
The single most useful question when auditing or extending the architecture:
|
|
219
|
+
|
|
220
|
+
> *"Does this layer actually call the wrapper, or does it only log locally?"*
|
|
221
|
+
|
|
222
|
+
Many framework-provided error fallbacks render a recovery UI and call a console-level logger but never report the event externally. From a user perspective the recovery feels graceful; from an operator perspective the production tracker shows zero events for that route, and a regression that fires the boundary 1000 times an hour is invisible.
|
|
223
|
+
|
|
224
|
+
The audit:
|
|
225
|
+
|
|
226
|
+
1. List every error-capture surface in the application (the four-surface model above).
|
|
227
|
+
2. For each surface, find the actual code path: which file owns the catch, what does it do with the error?
|
|
228
|
+
3. Confirm each path calls `reportError` (or, for Surface 3, the SDK directly with sanitization).
|
|
229
|
+
4. For surfaces that only log: decide whether they *should* report, and wire them up if so.
|
|
230
|
+
|
|
231
|
+
A common output of this audit: "47 routes have `error.tsx` files; 12 of them inherit a shared fallback that does report; 35 inherit a different shared fallback that only logs; we have been blind to errors on those 35 routes for a year."
|
|
232
|
+
|
|
233
|
+
## Evals
|
|
234
|
+
|
|
235
|
+
This skill ships a comprehension-eval artifact at [`examples/evals/error-tracking.json`](https://github.com/jacob-balslev/skill-graph/blob/main/examples/evals/error-tracking.json). The checklist below is the authoring gate for exception-reporting pipeline decisions; the eval file is the grader surface.
|
|
236
|
+
|
|
237
|
+
## Verification
|
|
238
|
+
|
|
239
|
+
- [ ] All application code reports errors through the wrapper module; nothing imports the tracker SDK directly except the application-global crash handler
|
|
240
|
+
- [ ] Every wrapper call passes payloads through `sanitizePII` before any external send
|
|
241
|
+
- [ ] User context uses internal IDs (`userId`, `orgId`, `role`); no email, name, phone, or address
|
|
242
|
+
- [ ] `isErrorTrackingEnabled()` is the single environment gate; dev mode falls through to the local logger
|
|
243
|
+
- [ ] Component boundaries (Surface 1) catch and call the wrapper
|
|
244
|
+
- [ ] Route fallbacks (Surface 2) explicitly call the wrapper — verify, do not assume
|
|
245
|
+
- [ ] Application-global handler (Surface 3) calls the SDK directly with its own sanitization (the wrapper may have crashed)
|
|
246
|
+
- [ ] Non-UI code (Surface 4) wraps async paths in try/catch and calls `reportError`
|
|
247
|
+
- [ ] Breadcrumbs are added at meaningful state transitions (route change, network call, mutation), with sanitization
|
|
248
|
+
- [ ] At least one regression test exercises a forced error and asserts the wrapper was called with sanitized data
|
|
249
|
+
- [ ] At least one regression test exercises an unconfigured environment and asserts the wrapper falls through to the logger
|
|
250
|
+
|
|
251
|
+
## Do NOT Use When
|
|
252
|
+
|
|
253
|
+
| Use instead | When |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `a11y` | Designing accessible error-message copy, focus management on the recovery UI, or screen-reader announcements |
|
|
256
|
+
| `debugging` | Root-causing a single observed error already captured in the tracker |
|
|
257
|
+
| `documentation` | Writing the contributor-docs page that explains the error-tracking architecture |
|
|
258
|
+
| `code-review` | Reviewing an AI-generated error handler for correctness |
|
|
259
|
+
| `testing-strategy` | Deciding whether a new error path warrants an integration regression test |
|
|
260
|
+
| `owasp-security` | Designing the broader PII storage, retention, and credential-handling policy across the system |
|
|
261
|
+
| `refactor` | Reorganizing the error-helper module while preserving its current behavior |
|