Exception audit
Repo-wide classification of exception handling patterns. Use when: auditing exception handling, finding swallowed exceptions, classifying catch patterns, finding overly-broad catches, or mapping unhandled-at-boundary throw sites in a C# solution. Optionally takes an exception type name to filter the audit.From its SKILL.md
npx -y skills add darylmcd/Roslyn-Backed-MCP --skill exception-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
12.6 KB, as published. Nobody here has run it
Exception Handling Audit
You are a C# exception-handling auditor. Your job is to load a workspace, enumerate every catch clause reachable from known exception types, classify each catch body by pattern (swallow, rethrow-as-is, rethrow-wrapped, delegate, handle), flag throw sites with no in-solution catch, and produce a ranked classification report that feeds downstream error-handling refactors.
Input
$ARGUMENTS is an optional exception type name (e.g., IOException, HttpRequestException, MyDomain.ValidationException). If provided, the audit is scoped to catches assignable from that type and throw sites of that type. If omitted, audit every exception type in use across the solution, with special attention to System.Exception itself (the broadest catch).
If a workspace is not already loaded, ask the user for the solution path and load it first.
Server discovery
Use server_info, resource roslyn://server/catalog, or MCP prompt discover_capabilities (analysis or all) for the live tool list and WorkflowHints. The primary tool for this skill is trace_exception_flow — confirm its presence in the catalog before starting.
Connectivity precheck
Before running any Roslyn MCP tool call, resolve the server's tool prefix once, then pin it.
The prefix is client-assigned — never hard-code it. Your MCP client derives it from the registration path it loaded the server under, so the same tool surfaces as
mcp__roslyn__server_infoon a dev-build/self-hosted entry, asmcp__plugin_roslyn-mcp_roslyn__server_infoon the marketplace-plugin install, and under a different prefix again for any other registration key. Those two are examples, not an allowed list — every prefix is valid, and a missing specific prefix literal is never itself grounds to halt.
-
Scan your current tool surface for every tool whose name ends in
server_info.server_infois a common MCP tool name, so expect more than one candidate and expect some to belong to other servers entirely. -
Identify the Roslyn one by its response shape, never by its prefix: a Roslyn
server_inforesponse carriesconnection.state,catalogVersion, andsurface.*. A candidate that returns anything else is simply not this server — that is not a halt condition, so try each remaining candidate before deciding. -
Pin the winning candidate's prefix and resolve every later bare tool name in this skill under that one pinned prefix only. Never re-resolve a later bare name by suffix across the whole surface: another MCP server may expose the same bare name (a Python/Jedi server has its own
find_referencesandget_symbol_outline), and matching it would silently attribute another server's results to Roslyn. -
Bail — report the message below to the user and stop the skill — only if no candidate returns a Roslyn-shaped response, or the pinned server reports
connection.stateas anything other than"ready"(initializing,degraded, absent):Roslyn MCP is not connected. This skill requires an active Roslyn MCP server: some tool whose name ends in
server_infomust be callable, return a Roslyn-shaped response (connection.state,catalogVersion,surface.*), and reportconnection.state: "ready". The prefix does not matter — your client assigns it from the registration path, so it may bemcp__roslyn__…,mcp__plugin_roslyn-mcp_roslyn__…, or anything else. Start the server (for exampledotnet tool run roslynmcp, or ensure the plugin's stdio entry is active in your client config), then re-run this skill once the Roslynserver_infotool reportsconnection.state: "ready"under whichever prefix your tool surface exposes. See the Connection-state signals reference for the canonical probes (server_info/server_heartbeat). -
Otherwise proceed with the rest of the workflow, issuing every tool call under the pinned prefix. The
server_infocall above also satisfies any server-version / capability-discovery needs — do not repeat it.
Workflow
Execute these steps in order. Use the Roslyn MCP tools — do not shell out or grep the source tree for catch clauses.
Step 1: Load Workspace
- Call
workspace_loadwith the solution/project path. Store the returnedworkspaceIdfor all subsequent calls. - Call
workspace_statusto confirm the workspace loaded successfully and note any load-time warnings. If the load fails, see Refusal conditions below.
Step 2: Seed the Exception Type Set
- If the user supplied a type filter in
$ARGUMENTS, usesymbol_searchto resolve it to a fully-qualified type symbol. Confirm withsymbol_info. If ambiguous, show candidates and ask the user to pick. - If no filter was supplied, seed the set with:
System.Exception(broadest catch — always audit).- Every exception type discovered by
symbol_searchfor names ending inExceptioninside the solution's source projects. - Framework exception types surfaced via
find_type_usageson throw sites (see Step 4).
Step 3: Enumerate Catch Clauses (primary)
For each exception type in the seed set:
- Call
trace_exception_flowwith the exception type. This returns every catch clause assignable from that type, including:- File / line / enclosing method (use
enclosing_symbolto confirm when the response lacks it). - The catch clause's declared exception type (may be a base type).
- A body excerpt.
- Rethrow-as annotations (
throw,throw new X(...),throw;— i.e., bare rethrow — and any wrapping chain).
- File / line / enclosing method (use
- Accumulate the catch clauses into a single table keyed by
(file, line, enclosingSymbol). - Separately, use
semantic_searchwith queries such as"catch (Exception"and"catch ("to surface any catches the exception-flow trace missed (e.g., variable-less catches, filter expressions). Merge new entries into the accumulator.
Step 4: Enumerate Throw Sites
For each exception type in the seed set:
- Use
symbol_search(kindMethod/Constructor) plusfind_referenceson the exception type's constructors to locate throw sites across the solution. - Record
(file, line, enclosingSymbol, exceptionType)for eachthrow new X(...)and barethrow;outside a catch.
Step 5: Classify Each Catch Body
For every catch clause captured in Step 3, classify the body using this procedure:
- Call
analyze_control_flowon the catch block span to determine whether it exits normally, rethrows, or returns. - Read the body excerpt from
trace_exception_flow(or pull the enclosing span viaget_source_text/get_syntax_treeif the excerpt is truncated). - Apply the Classification Rubric below to assign one of:
swallow,swallow-with-log,rethrow-as-is,rethrow-wrapped,delegate,handle-and-continue, orunknown. - For
delegatecandidates (catch body calls another method that may itself throw), usecallers_calleeson the enclosing method to record the propagation chain one level up. Do not recurse indefinitely — one hop is enough for the report.
Step 6: Identify Possible Unhandled-at-Boundary
For every throw site captured in Step 4:
- Use
callers_calleeson the throwing method to walk the call graph upward. - At each frame, check whether an accumulated catch clause (from Step 3) covers the exception type (by assignability — use
symbol_info/type_hierarchyif needed). - If the walk reaches a public entry point (program
Main, ASP.NET controller action,Taskreturned from a top-level handler, test method, etc.) without finding a covering catch, flag the throw site as possible unhandled-at-boundary. Record the entry-point frame. - If the walk is cut off by recursion, virtual dispatch into unknown implementations, or a delegate boundary, mark the site
unknown-boundaryrather thanunhandled— the classifier is conservative.
Step 7: Aggregate and Rank
Aggregate Steps 3-6 into a single ranked report:
| Tier | Signal |
|---|---|
| P0 — Critical | Empty catch (Exception) body (pure swallow of the broadest type); catch (Exception) that logs-and-continues at a domain boundary; unhandled-at-boundary throw of a non-recoverable exception type. |
| P1 — High | Non-empty catch (Exception) (broad catch) that does not rethrow; rethrow-wrapped that replaces InnerException with null; rethrow via throw ex; (stack-trace loss). |
| P2 — Medium | Typed swallow-with-log (e.g., catch (IOException) { _log.Warn(...); }); rethrow-as-is where a typed wrapper would be more appropriate; catch with a filter expression whose branches mix swallow and rethrow. |
| P3 — Low | Typed handle-and-continue with a sound recovery path; delegate catches whose inner method is a thin wrapper; informational / style-only findings. |
Sort by tier then by frequency-of-pattern descending; break ties by file:line ascending.
Step 8: Close Workspace
Call workspace_close to release resources.
Classification Rubric
| Pattern | Signal (what to look for in the catch body) | Severity |
|---|---|---|
| swallow | Body is empty, or contains only a comment / return; / return default; with no log or side effect. analyze_control_flow reports normal exit. | P0 if catch (Exception), else P1 |
| swallow-with-log | Body is exactly: log/trace call(s) and then falls through. No rethrow, no recovery state change. | P1 if catch (Exception), else P2 |
| rethrow-as-is (safe) | Body ends with bare throw;. Stack trace preserved. | P3 |
| rethrow-as-is (unsafe) | Body ends with throw ex; (named variable) — overwrites the stack trace. | P1 |
| rethrow-wrapped | Body ends with throw new X(..., ex); where ex is passed as innerException. | P3 if wrapper is typed domain exception; P1 if innerException is dropped. |
| delegate | Body calls another method (e.g., HandleError(ex), Policy.Handle(ex)) and the catch exits normally. Requires a one-hop callers_callees probe to resolve. | P2 |
| handle-and-continue | Body mutates recoverable state (e.g., sets a fallback, returns a default value) and exits normally. Typed catch only. | P3 |
| broad-catch | Clause is catch (Exception) or catch (no type / no variable) regardless of body — tag every such clause in addition to its body classification. | Promote the body's tier by one (min P1). |
| unknown | Control flow or body excerpt could not be classified confidently. | P2 (flag for manual review) |
Output Format
Present a structured report with these sections:
## Exception Handling Audit: {solution-name}{ for {filter-type} if filtered}
### Summary
- Exception types audited: {count}
- Catch clauses found: {count}
- Throw sites found: {count}
- Possible unhandled-at-boundary: {count}
- Broad catches (`catch (Exception)` or variable-less): {count}
### Catch Population
{table — columns: Exception Type, Total Catches, Swallow, Swallow+Log, Rethrow-as-is, Rethrow-wrapped, Delegate, Handle, Unknown}
### Swallowed Exceptions
{table — columns: #, Tier, file:line, Enclosing Symbol, Catch Type, Body Excerpt (one-line), Notes}
### Broad Catches (`catch (Exception)` / variable-less)
{table — columns: #, Tier, file:line, Enclosing Symbol, Body Classification, Rationale}
### Possible Unhandled-at-Boundary
{table — columns: #, Throw Site (file:line), Exception Type, Enclosing Method, Reached Boundary, Propagation Chain}
### Recommendations
{top 3-5 refactors — e.g., "Replace `throw ex;` at X with bare `throw;` (use refactor skill)", "Narrow `catch (Exception)` at Y to `catch (IOException, TimeoutException)`", "Wrap throw site Z in a typed domain exception at the API boundary". Each recommendation cites the suggested next tool or skill (e.g., `code-actions`, `refactor`, `code_fix_preview` for a diagnostic ID).}
Refusal conditions
If workspace_load fails or workspace_status reports the workspace did not load, stop the skill and report the failure with any load-time diagnostics returned by workspace_status. Do not attempt any of Steps 2-8 without a loaded workspace — the catch-graph enumeration requires semantic symbols that are only available after a successful load.