diagnostics
Error handling and build-time diagnostics conventions - Result-not-panic, structured Diagnostics with stable codes, actionable help, color/JSON presentation layering, exit codes, and cold-path performance.
Install
mkdir -p .claude/skills/diagnostics && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16628" && unzip -o skill.zip -d .claude/skills/diagnostics && rm skill.zipInstalls to .claude/skills/diagnostics
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Error handling and build-time diagnostics conventions - Result-not-panic, structured Diagnostics with stable codes, actionable help, color/JSON presentation layering, exit codes, and cold-path performance.About this skill
Error Handling & Diagnostics
Use this skill whenever you add, change, or review an error path: a build-time authoring error, a parser/handler failure, a CLI validation error, or anything surfaced to a host (FFI/WASM/Node) or a tool/agent. WebUI errors must be recoverable, actionable, and machine-consumable — for humans and AI agents alike.
1 - Never panic on recoverable input
panic = "abort" in the release profile means a panic kills the process
instantly — including any FFI/WASM/Node host embedding the framework. Bad
template input, bad CLI input, and bad state are recoverable and must return
Result, never panic!/unwrap()/expect().
| Situation | Do |
|---|---|
| Malformed template / CSS / route authored by a developer | Return Result with a structured Diagnostic (see §3). |
| Missing/invalid CLI input (file, port, flag) | Return a typed CliError (see §6). |
| A genuinely impossible internal state | Prefer ? with a typed error; only use unreachable!/expect with a justification comment, and never in a hot or host-reachable path. |
unwrap()/expect() are banned in library code (clippy.toml
disallowed-methods). todo!, unimplemented!, and dbg! are banned
workspace-wide (clippy.toml disallowed-macros). Tests opt out with
#[allow(clippy::disallowed_methods)].
Enforcement note.
unwrap/expect/todo!/unimplemented!/dbg!are caught by clippy.panic!is not lint-banned (too entrenched) — keep it out of recoverable paths by review. The "no regex in core logic" rule is also review-enforced, notdeny-banned:actix-webpullsregextransitively, so a crate-level ban would break the build and can't scope to first-party code.
2 - Error type conventions
| Crate kind | Error type |
|---|---|
Library (webui-parser, webui-handler, webui-expressions, webui-state, webui-protocol, webui-ffi) | Custom enum via thiserror. |
Binary (webui-cli, xtask) | anyhow for orchestration; a typed enum when callers must branch on the cause (e.g. webui-cli's CliError for hints + exit codes). |
- Each error layer's
Displaydescribes only its own level; the#[source]chain carries the rest, soanyhow's{:#}never double-prints. Provide a flatchain_message()helper for hosts that don't walk the chain (Node, FFI). - Add dedicated variants instead of overloading a generic
Generic(String)/Validation(String)so callers canmatchprogrammatically.
3 - Authoring errors are structured Diagnostics
Every "the developer wrote invalid template syntax" mistake is returned as
ParserError::Template(Box<Diagnostic>) (crates/webui-parser/src/diagnostic.rs),
so all build errors render identically. A Diagnostic carries:
code— a stable, machine-readable identifier (e.g.invalid-for-each). Defined indiagnostic::codes. Treat codes as a stable API: tools and AI agents branch on them, so rename only with a deliberate migration.- title — short, lowercase (
invalid <for> each expression). - location — set from a byte offset via
.at_offset(source, offset); rendered rustc-style--> owner:line:column(single forward scan, no regex, no recursion), falling back toin component <c> · element <e>. - snippet — the offending source text.
help:— an actionable fix (see §4).
Add a new authoring error with the parser helpers (authoring_error,
authoring_error_at, html_error) and a new constant in diagnostic::codes.
Validate at parse/build time and fail fast — never defer to render time.
4 - Make errors actionable (and typo-aware)
Tell the developer what is wrong and how to fix it. Every Diagnostic
should carry a help: line. Where a mistake is likely a typo, suggest the
intended name via suggest::closest_match (iterative Levenshtein — no
recursion, no regex, cold path only):
- Misspelled directive attribute:
<for eahc=…>-> "did you meaneach?" - Unknown component tag: suggest the closest same-namespace registered
component (
<mp-buton>-><mp-button>). Prefix-guard the match (text before the first-must match) so a genuine third-party custom element (<md-button>) is never falsely flagged.
5 - Presentation layering: color belongs ONLY in the entry point
Libraries produce plain, color-free data. The entry point decides how to present it. Never embed ANSI in library output or in any machine/host channel.
| Consumer | Gets |
|---|---|
webui-cli (terminal) | Reads Diagnostic fields and colorizes with console::style() — the only approved styling method (see copilot-instructions "Terminal output styling"). |
| FFI / WASM / Node | The plain Display text through their native error channel (webui_last_error, JsValue, napi::Error). |
Browser / tools (dev-server live-reload, SSE, console.error) | Plain text. ANSI renders as garbage and breaks single-line SSE frames. |
When one value feeds both a terminal and a non-terminal channel, split it:
webui-dev-server's RebuildError { display, message } carries a colorized
display for the reporter and a plain message for the browser.
Per-line color: when colorizing multi-line output, style each line
independently (open + close the SGR span within the line). A single span that
straddles newlines bleeds when the line is later re-prefixed (e.g. [server]
under xtask dev).
6 - Machine-readable output and exit codes (webui-cli)
For editors, CI, and AI/agent tooling:
--format json(global flag) emits each error as one JSON object on stdout (no ANSI; decorative output suppressed):{severity, code, message, file, line, column, snippet, help, chain}. Branch on the stablecode, not the humanmessage. Build the object with theserde_json::MapAPI, not thejson!macro (itunwraps internally and tripsdisallowed_methods).- Exit codes follow BSD
sysexits.h(webui-cli'serror::exit_code):65data/authoring error,66missing input,69port in use,74I/O,2usage (clap),1otherwise. - Replace fragile
err_msg.contains("...")dispatch with typed errors that own theirhint()andexit_code().
7 - Error construction is COLD - keep it off the hot path
Building a Diagnostic (format strings, suggestions, location scans) is rare,
but if it inlines into a hot function it bloats that function and perturbs its
code layout — a real, measurable regression (observed ~4-5% on parse benches
with no added hot-path work).
- Mark error builders
#[cold]+#[inline(never)](e.g.authoring_error*,html_error,css_diagnostic,*_errorconstructors,suggest::closest_match). - Keep hot fast-paths inlinable: a per-element check (e.g.
split_once('-')) must stay inlined; only its cold fallback (the registry scan) goes out-of-line. - Validate layout-sensitive changes with
cargo bench -p <crate>against the base branch (seeskills/perf/SKILL.md). A "regression" with no added compute is usually layout — fix it with#[cold], don't shrug it off.
8 - Checklist for a new error
- Returned as
Result, never panicked (host-safe underpanic = "abort"). - Authoring mistake ->
ParserError::Template(Box<Diagnostic>)with a newdiagnostic::codesconstant, location, snippet, andhelp:. -
help:is actionable; add a "did you mean …?" suggestion if it's a typo. - No color/ANSI in library, host, browser, or JSON output.
- Surfaced in
--format jsonwith the stablecode; exit code classified. - Error construction is
#[cold]/#[inline(never)]; hot path unchanged (confirm with a benchmark if it sits near a hot loop). - Tests: a regression test that fails without the error, asserting on the
code(not the prose); JSON stays plain (no\x1b). -
DESIGN.mdanddocs/(incl.docs/ai.md) updated if the contract or a user-visible code/flag changed.