csharp-debugging
Skill2 starsChanged 42 days agoMITView on GitHub
Use when a C#/.NET bug, exception, test failure, or wrong behavior needs diagnosing. Enforces a hypothesis-driven loop — reproduce, read the real exception, isolate, change one thing, verify the root cause — instead of guessing and trying random fixes.
In this repository56 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- benchmarkingSkill
- commit-messagesSkill
- csharp-15-syntaxSkill
- csharp-aot-trimmingSkill
- csharp-api-designSkill
- csharp-assembly-inspectionSkill
- csharp-asyncSkill
- csharp-collections-modernSkill
- csharp-concurrency-primitivesSkill
- csharp-data-layoutSkill
- csharp-discardsSkill
- csharp-disposable-patternsSkill
- csharp-docsSkill
- csharp-error-handlingSkill
- csharp-extension-membersSkill
- csharp-generics-modernSkill
- csharp-guard-clausesSkill
- csharp-json-source-generationSkill
- csharp-language-versionsSkill
- csharp-logging-diagnosticsSkill
- csharp-lspSkill
- csharp-modern-typesSkill
- csharp-nullabilitySkill
- csharp-nullable-migrationSkill
- csharp-nunitSkill
- csharp-pattern-matchingSkill
- csharp-performanceSkill
- csharp-polyfillsSkill
- csharp-source-generatorsSkill
- csharp-static-lambdasSkill
- csharp-string-handlingSkill
- csharp-tunitSkill
- csharp-verificationSkill
- dispatching-subagentsSkill
- dotnet-11-librariesSkill
- dotnet-analyzersSkill
- dotnet-central-package-managementSkill
- dotnet-file-based-appsSkill
- dotnet-msbuild-usingsSkill
- dotnet-multi-targetingSkill
- dotnet-project-hygieneSkill
- dotnet-runSkill
- dotnet-runtime-asyncSkill
- dotnet-slnxSkill
- dotnet-test-platformsSkill
- executing-plansSkill
- performance-bounded-deduplicationSkill
- roslyn-analyzer-performanceSkill
- roslyn-discoverySkill
- roslyn-duplicate-detectionSkill
- roslyn-guard-rewritersSkill
- roslyn-rewritersSkill
- subagent-driven-developmentSkill
- tddSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 3 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
If your instinct is "let me just try changing X" before you understand the failure, stop.
- Very specific: line 30
Don't ignore compiler warnings and analyzer hints — a nullable warning (CS86xx) often *is* the bug.
- Very specific: line 41
Test it with the smallest probe: a breakpoint, one log line, a conditional breakpoint (when (id == 42)), Debug.Assert, or a focused unit …
- Distinctive: line 42
Change one variable at a time: If you change three things and it works, you don't know which mattered — and may have added two new bugs.
- Very specific: line 57
Concurrency: if it only fails under load or parallel tests, suspect shared mutable state, a missing await, or a captured …
- Distinctive: line 63
Explain it: If you can't say in one sentence why the bug happened and why the fix addresses it, you probably patched a symptom.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Systematic debugging (.NET)
Random fixes waste time and often paper over the real bug. Debugging is a disciplined loop: reproduce → read → isolate → hypothesize → change one thing → verify. Don't skip to "try a fix" — you can't fix what you haven't located.
Phase 0 — resist the guess
If your instinct is "let me just try changing X" before you understand the failure, stop. That's how symptoms get masked and root causes survive. Form a hypothesis you can test first.
1. Reproduce reliably
You cannot fix what you can't trigger on demand.
- Get it failing consistently. An intermittent bug is usually a timing/ordering/state issue — pin it down before fixing (see the async + execution-context notes in
csharp-async/csharp-tunit). - Capture the smallest repro: a failing test is ideal. Write one if you can — it both reproduces and becomes the regression guard (the
tddskill's bug-fix flow). - Note the exact inputs, environment, and runtime (
net8/net9/AOT vs JIT) — behavior can differ.
2. Read the actual error — all of it
Most .NET bugs announce themselves precisely. Read the whole thing before theorizing.
- Exception type + message tell you the category.
NullReferenceException,InvalidOperationException("Collection was modified"),ObjectDisposedException,TaskCanceledExceptioneach point somewhere specific. - Read the full stack trace top-down to the first frame in your code. That line is the crime scene.
- Unwrap aggregates.
AggregateException.Flatten().InnerExceptionsand theInnerExceptionchain hide the real cause — sync-over-async (.Result/.Wait()) wraps the true exception. Look inside. - Async stack traces can be shallow. Enable first-chance exception breaks in the debugger to catch the throw at its origin, not where it surfaced.
- Don't ignore compiler warnings and analyzer hints — a nullable warning (
CS86xx) often is the bug.
3. Isolate — shrink the search space
- Binary search the cause. Bisect by code (comment out / git bisect across commits) or by data (which input row triggers it?). Halve the suspect space each step.
- Minimal repro. Strip away everything that still leaves the bug present. What remains is the bug.
- Check the boundaries. Bugs cluster at edges: null/empty, first/last element, zero, concurrency races, disposed objects, culture-sensitive parsing/formatting, time zones.
4. Hypothesize, then test one thing
- State a falsifiable hypothesis: "the list is mutated while enumerated in
Foo." - Test it with the smallest probe: a breakpoint, one log line, a conditional breakpoint (
when (id == 42)),Debug.Assert, or a focused unit test. - Change one variable at a time. If you change three things and it works, you don't know which mattered — and may have added two new bugs.
Debugging a CI failure specifically
- Read the actual failing log before theorizing. Open the failing job's log (e.g.
gh run view <run-id> --log-failed) and find the real error line before proposing a cause — never blame config, infra, or "CI flakiness" without evidence from the log. - Check whether the run is even current. Compare the failing run's commit against the branch head; if the head already contains a fix, the run is stale and the answer is a re-run, not a code change.
- "Flaky" is not a finding. A test that fails under load or only intermittently is telling you the code has a race, a swallowed exception, or a visibility gap — read the failure, find the mechanism, and fix that, rather than re-running until it passes.
- Never harden a test to hide a real defect. Widening a tolerance, adding a retry, or weakening an assertion to make an intermittent failure stop showing up papers over the bug instead of fixing it (see
csharp-verification).
.NET tools to reach for
- Debugger: conditional breakpoints, first-chance exception settings (Break When Thrown), watch/immediate window, "Just My Code" off to step into framework.
- Logging: structured logs at the suspected path; check log level before formatting to avoid noise.
dotnet-tracefor CPU/perf and event flow;dotnet-dump+dotnet-dump analyzefor hangs/deadlocks (clrstack,syncblk);dotnet-countersfor live GC/thread-pool/allocation pressure.- Memory/leak: a dump + heap analysis; watch for event-handler and static-reference roots, undisposed
IDisposable. - Concurrency: if it only fails under load or parallel tests, suspect shared mutable state, a missing
await, or a capturedSynchronizationContext— reproduce deterministically rather than adding sleeps.
5. Fix the root cause, then verify
- Fix the cause, not the symptom. Catching and swallowing the exception, adding a null check at the crash site, or widening a timeout usually hides the bug — ask why the value was null / the collection changed / the task cancelled.
- Verify properly: the repro/test now passes, and it failed before your change (prove the test bites). Run the full suite — your fix shouldn't break neighbors.
- Explain it. If you can't say in one sentence why the bug happened and why the fix addresses it, you probably patched a symptom.
Anti-patterns
- Trying fixes before reproducing or reading the exception.
- Adding
try/catchto make an error "go away." - Changing several things at once.
- "It works now" without understanding why it was broken.
- Blaming the framework/compiler first — it's almost always your code or your assumptions.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

