<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rishi Gulati</title>
    <description>The latest articles on DEV Community by Rishi Gulati (@firish).</description>
    <link>https://dev.to/firish</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3999260%2F0d126494-689b-471d-81a3-8bca21968e90.png</url>
      <title>DEV Community: Rishi Gulati</title>
      <link>https://dev.to/firish</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/firish"/>
    <language>en</language>
    <item>
      <title>Claude Code in Visual Studio: now it drives the debugger, catches flaky tests, and reads library source</title>
      <dc:creator>Rishi Gulati</dc:creator>
      <pubDate>Wed, 08 Jul 2026 18:41:05 +0000</pubDate>
      <link>https://dev.to/firish/claude-code-in-visual-studio-now-it-drives-the-debugger-catches-flaky-tests-and-reads-library-35b6</link>
      <guid>https://dev.to/firish/claude-code-in-visual-studio-now-it-drives-the-debugger-catches-flaky-tests-and-reads-library-35b6</guid>
      <description>&lt;p&gt;Claude Code has this nice thing where it plugs into your editor. On VS Code or a JetBrains IDE, its edits open as a diff you accept or reject, it reads your compiler errors, and it knows what you have selected. On Visual Studio you get none of that. You copy and paste into a terminal like it is 2022. There is an &lt;a href="https://github.com/anthropics/claude-code/issues/15942" rel="noopener noreferrer"&gt;open issue&lt;/a&gt; asking for Visual Studio support with a few hundred upvotes and no ETA, so I built the Visual Studio side myself.&lt;/p&gt;

&lt;p&gt;It is a native VS 2026 extension. The important thing to say up front is what it is &lt;em&gt;not&lt;/em&gt;: it is not another agent. The &lt;code&gt;claude&lt;/code&gt; CLI does all the thinking. My extension is the IDE half of Claude Code's integration protocol, which I had to reverse-engineer off the wire since it is undocumented. It makes zero model calls of its own. It is a bridge.&lt;/p&gt;

&lt;p&gt;The first thing you get is the diff gate. When Claude wants to change a file, the change opens in Visual Studio's own diff viewer, and approving there is the only step. No second yes/no prompt in the terminal. You can reject with a note, and it reconsiders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdxhgff2rbulkairvmss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdxhgff2rbulkairvmss.png" alt="A Claude edit open in the Visual Studio diff viewer with Accept, Reject, and Reject with feedback" width="799" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That part was the plan. The part I did not expect is what became possible &lt;em&gt;once the IDE half existed&lt;/em&gt;. Because I control the Visual Studio side, I could hand the agent things the CLI can never reach on its own. The one I keep coming back to is the debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watching the code run, instead of reading it
&lt;/h2&gt;

&lt;p&gt;Here is a scoring function that returns the wrong total. Nothing in the source reads as wrong. The final number is just off. A fresh Claude session, with no idea what the bug was, was asked to check it, and it drove the Visual Studio debugger to find it in about ninety seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj772pssez5eq3tgrklr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj772pssez5eq3tgrklr.png" alt="A Claude edit open in the Visual Studio diff viewer with Accept, Reject, and Reject with feedback" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It set a breakpoint at the top of the loop, started the session, and stepped through round by round, watching a &lt;code&gt;combo&lt;/code&gt; counter. Here is the trace it kept, for the input &lt;code&gt;{5, 3, 0, 4, 2, 0, 6}&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fje6o4h4z5d14wt11wih6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fje6o4h4z5d14wt11wih6.png" alt="A Claude edit open in the Visual Studio diff viewer with Accept, Reject, and Reject with feedback" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bug is that the combo multiplier never resets on a zero round. &lt;code&gt;if (points &amp;gt; 0)&lt;/code&gt; and &lt;code&gt;else if (points &amp;lt; 0)&lt;/code&gt; both miss &lt;code&gt;points == 0&lt;/code&gt;, so the streak carries across a zero and inflates the score. You cannot see that by reading the code. It only shows up when you watch the counter stay put across the round it should have cleared. That is the whole difference between reading code and debugging it, and the agent got to do the second one.&lt;/p&gt;

&lt;p&gt;Reading runtime state is always on. Driving the debugger, actually stepping and setting breakpoints and running your code, is opt-in behind a toggle in the panel that resets every session, because it runs your program under the model's control.&lt;/p&gt;

&lt;h2&gt;
  
  
  It grew past stepping
&lt;/h2&gt;

&lt;p&gt;Once the drive loop worked, the rest followed. Claude can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attach to a process that is already running.&lt;/strong&gt; A hosted web service or a desktop app, not just an F5 launch. It lists the processes, attaches, arms a first-chance exception, triggers the request itself with &lt;code&gt;curl&lt;/code&gt;, and stops at the throw site inside the handler instead of the generic &lt;code&gt;catch&lt;/code&gt; that swallowed it into a bland 500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untangle a deadlock.&lt;/strong&gt; A deadlocked program never hits a breakpoint, so a breakpoint is useless. Claude pauses the hung process, reads each stuck thread's wait chain, and reconstructs the exact &lt;code&gt;A -&amp;gt; B -&amp;gt; C -&amp;gt; A&lt;/code&gt; lock cycle, correctly ignoring the threads that are merely idle or busy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the one I am most proud of, because Visual Studio's own UI cannot even set it through an automation API: &lt;strong&gt;data breakpoints.&lt;/strong&gt; Point Claude at a field and it stops the instant that field is written, or records every value the field takes, in order. It can watch conditionally (break only when the value goes below zero) and watch several fields at once.&lt;/p&gt;

&lt;p&gt;I built a little fixture where an order's total is mutated through four different pricing methods and comes out negative, with no single line to breakpoint. Claude armed a plain watch, a conditional watch, and a second watch at once:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvm2urr46q1x8c5i1v96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvm2urr46q1x8c5i1v96.png" alt="Claude arming a plain, conditional, and multi-watch data breakpoint in one call, with the vs_set_data_breakpoint tool use" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The conditional watch broke exactly on the write that corrupted the total, and the plain watch recorded the full timeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;value (cents)&lt;/th&gt;
&lt;th&gt;written by&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-3&lt;/td&gt;
&lt;td&gt;0 → 30000 → 42000 → 54000&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AddItems&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;three line items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;54000 → 48600&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ApplyBulkDiscount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10% off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;48600 → 52488&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ApplyTax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;+8% tax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;52488 → &lt;strong&gt;-47512&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ApplyLoyaltyCredit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the corruption&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Getting that to work was the hardest part of the whole project. There is no EnvDTE surface for it, so it rides a small Concord debug-engine component that ships with the extension and arms the breakpoint from inside the engine, then streams the changes back out over file IPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching a flaky test red-handed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnhuvyzfh449kocyy13g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjnhuvyzfh449kocyy13g.png" alt="A Claude edit open in the Visual Studio diff viewer with Accept, Reject, and Reject with feedback" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet test&lt;/code&gt; runs your tests. What it cannot do is stop &lt;em&gt;inside&lt;/em&gt; a failing one in the debugger, or reproduce an intermittent failure on purpose and pause on it. So I wired Visual Studio's Test Explorer engine to the same debug session.&lt;/p&gt;

&lt;p&gt;The signature move is catching a heisenbug. Point it at a flaky test and it loops the test under the debugger with break-on-thrown armed, lets the passing runs finish, and stops on the first failing run, paused at the throw with the exception live in the frame:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9y5zxiqorrqcdr7iy7s5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9y5zxiqorrqcdr7iy7s5.png" alt="The Visual Studio debugger paused inside a flaky test at the throw site, with the exception live in the Locals window" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The honest twist in that run: when it read the frame, it found there were no captured locals at all, and the throw came straight out of an &lt;code&gt;if (Random.Shared.Next(3) == 0)&lt;/code&gt; branch. The test was not exposing a product bug. The test itself was nondeterministic because it branched on an unseeded RNG. A re-run loop would have given you a different red line every time and never told you that. Being paused inside the exact failing run did.&lt;/p&gt;

&lt;p&gt;Getting real per-test results out of the engine took a trick too. Its result callback is an internal interface you cannot implement in normal C#, so the extension emits a type that implements it at runtime with &lt;code&gt;Reflection.Emit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading code the way the compiler does
&lt;/h2&gt;

&lt;p&gt;The last piece is less flashy but I use it constantly. Instead of grepping text for "where is this used," Claude asks Visual Studio's compiler (Roslyn) for the resolved answer: find-all-references, go-to-definition, find-implementations, and call and type hierarchies, resolved through interfaces, overrides, and overloads. It catches the cases text search gets wrong, like an explicit interface implementation or a virtual dispatch a &lt;code&gt;grep&lt;/code&gt; can never tie back to the interface.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it caught that a text search misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;find-implementations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Triangle&lt;/code&gt;'s &lt;strong&gt;explicit&lt;/strong&gt; &lt;code&gt;IShape.Area&lt;/code&gt;, which &lt;code&gt;.Area()&lt;/code&gt; or &lt;code&gt;: IShape&lt;/code&gt; never ties back to the interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;type-hierarchy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Circle&lt;/code&gt; and &lt;code&gt;Square&lt;/code&gt; reach &lt;code&gt;IShape&lt;/code&gt; transitively through &lt;code&gt;ShapeBase&lt;/code&gt;; grep on &lt;code&gt;: IShape&lt;/code&gt; finds only the two direct implementers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;call-hierarchy&lt;/td&gt;
&lt;td&gt;the interface-dispatched callers of &lt;code&gt;IShape.Area&lt;/code&gt;, back to &lt;code&gt;Main&lt;/code&gt;, that a search on the concrete &lt;code&gt;Circle.Area&lt;/code&gt; would miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;go-to-definition&lt;/td&gt;
&lt;td&gt;the right &lt;code&gt;Describe&lt;/code&gt; overload at a call site, not all three declarations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decompile&lt;/td&gt;
&lt;td&gt;the real body of &lt;code&gt;Enumerable.Sum&lt;/code&gt; from the library, with &lt;code&gt;Math.Sqrt&lt;/code&gt; falling back to SourceLink&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It also does the one thing reading your repo fundamentally cannot: it decompiles the body of a method that lives in a referenced DLL. Point it at a framework or NuGet call and it hands back the real C#, the way Go-To-Definition does, and for core .NET types it fetches the actual runtime source over SourceLink.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it hangs together
&lt;/h2&gt;

&lt;p&gt;If you like the plumbing: the extension starts a localhost WebSocket server, writes a lockfile, and launches &lt;code&gt;claude&lt;/code&gt; already connected. The diff, diagnostics, and selection tools ride Claude Code's IDE protocol directly. The debugger, the test runner, and the semantic tools ride two extra MCP servers the CLI reaches through a tiny stdio shim, because that channel is the open plugin door where the model actually sees the tools. Everything runs in-process against the live Visual Studio services, on the UI thread where it has to be. The CLI does all the agent work; the extension never calls a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Visual Studio 2026 only for now. A 2022 backfill is possible if people want it.&lt;/li&gt;
&lt;li&gt;The integration protocol is undocumented and could change on any CLI update, so I pin and smoke-test against a known-good version.&lt;/li&gt;
&lt;li&gt;The debugger, test, and data-breakpoint features are .NET-focused.&lt;/li&gt;
&lt;li&gt;It is free, MIT, and not affiliated with Anthropic. "Claude" and "Claude Code" are Anthropic's.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you write C# and use Claude Code, I would genuinely like to know how it holds up on a real codebase.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Marketplace:&lt;/strong&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=firish.bridgev1" rel="noopener noreferrer"&gt;Claude Code for Visual Studio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source and docs:&lt;/strong&gt; &lt;a href="https://github.com/firish/claude_code_vs" rel="noopener noreferrer"&gt;github.com/firish/claude_code_vs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deep-dives (the full debugger tool list, the test loop, the semantic surface) are in the repo's &lt;code&gt;docs/&lt;/code&gt; folder if you want to see how far each piece goes.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>visualstudio</category>
    </item>
    <item>
      <title>I brought Claude Code to Visual Studio, then handed it the debugger</title>
      <dc:creator>Rishi Gulati</dc:creator>
      <pubDate>Tue, 23 Jun 2026 19:00:23 +0000</pubDate>
      <link>https://dev.to/firish/i-brought-claude-code-to-visual-studio-then-handed-it-the-debugger-4k5</link>
      <guid>https://dev.to/firish/i-brought-claude-code-to-visual-studio-then-handed-it-the-debugger-4k5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; this extension has grown well past what this post covers. It now has a debugger Claude can drive, data breakpoints, test integration, and Roslyn code navigation. The current, fuller walkthrough is here: &lt;a href="https://dev.to/firish/claude-code-in-visual-studio-now-it-drives-the-debugger-catches-flaky-tests-and-reads-library-35b6"&gt;I brought Claude Code to Visual Studio, and gave it a debugger it can drive&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Plenty of times I've run a C# project in VS Code instead of Visual Studio, not because I like it better, but because that's where Claude could show me its edits in a real diff before I accept or reject. There's already &lt;a href="https://github.com/anthropics/claude-code/issues/15942" rel="noopener noreferrer"&gt;an open request for something similar on GitHub&lt;/a&gt; with a stack of upvotes and no answer yet. So, I built the missing half myself.&lt;/p&gt;

&lt;p&gt;It started as a diff window. I wanted Claude's edits to show up in the real Visual Studio diff instead of a yes/no prompt in a terminal. That worked, and so, I moved to the next biggest inconvenience, letting the Claude CLI read the compiler errors and warnings, the lint problems, and the rest of the build output directly, instead of copy-pasting them in. This was the v1 of my project.&lt;/p&gt;

&lt;p&gt;The next biggest problem (and opportunity) came when I found myself using the debugger, and adding console logs and then hand-feeding those values to the Claude CLI to get it to debug a tricky issue with the correct context. So I started building a bridge between Visual Studio's debugger automation and the CLI. A few weeks later Claude was setting breakpoints in my files, stepping through them, watching variables mutate at different frames, and telling me where a bug was hiding that it would have probably skimmed past while reading my code.&lt;/p&gt;

&lt;p&gt;Quick disclaimer first. This is an unofficial community project, not affiliated with Anthropic. It's open source, it targets Visual Studio 2026, and it needs the Claude Code CLI installed. The Anthropic CLI is the hero doing the actual model work. My extension is the IDE sidekick supporting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The run that made me excited
&lt;/h2&gt;

&lt;p&gt;Check out this scoring function. Each round scores points, and a run of scoring rounds builds a combo multiplier. The first hit counts once, the second in a row counts double, and so on. A round that scores nothing should break the run and reset the combo.&lt;/p&gt;

&lt;p&gt;It compiles. It runs. It prints the wrong number. Give it the rounds &lt;code&gt;{5, 3, 0, 4, 2, 0, 6}&lt;/code&gt; and it returns 61, when the answer is 25. Nothing in the source looks wrong. No IDE warnings.&lt;/p&gt;

&lt;p&gt;I opened a fresh Claude session that had never seen the file. Then I asked it to find what was tripping up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj772pssez5eq3tgrklr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffj772pssez5eq3tgrklr.png" alt="Paused inside the loop, with the locals sitting right there" width="800" height="767"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It stepped through the rounds one at a time, reading the combo and the running total at each stop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxeqkuhkpz9zj8vzy9yo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxeqkuhkpz9zj8vzy9yo.png" alt="Claude driving the debugger, step by step" width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the trace it built as it went:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;round&lt;/th&gt;
&lt;th&gt;points&lt;/th&gt;
&lt;th&gt;combo after&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;th&gt;expected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fje6o4h4z5d14wt11wih6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fje6o4h4z5d14wt11wih6.png" alt="The same trace, the way Claude laid it out" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Round 2 is the tell. The points are 0, so the combo should drop back to nothing. It stays at 2. Same at round 5. The run never resets, so every later round gets multiplied by a number that's too big and the total climbs to 61.&lt;/p&gt;

&lt;p&gt;Then it pointed at the cause:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybixuljim1gedvwaow06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybixuljim1gedvwaow06.png" alt="The diagnosis and the fix" width="799" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The zero case falls through both branches. The code handles points greater than zero and points less than zero, and a plain zero matches neither, so the combo never resets.&lt;/p&gt;

&lt;p&gt;The bug is invisible in the output. The only way to catch it is to watch the combo fail to reset while the program runs, which is what a person does with a debugger (or a bunch of tracing statements), and what the agent did here. It didn't read the bug. It watched it. Start to finish, about a minute and a half.&lt;/p&gt;

&lt;p&gt;Then it wrote the fix, and the fix opened where I would want it. Not as text in a terminal, but in Visual Studio's own diff viewer, with Accept and Reject right there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1s82venxuj5qc1i7t9xo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1s82venxuj5qc1i7t9xo.png" alt="Claude's fix in the native diff viewer" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The everyday part it rides on
&lt;/h2&gt;

&lt;p&gt;That diff is the part I had been leaning on for weeks before any of the debugger work, and it's still what I use most. Every change Claude proposes opens as a real diff in the editor you already trust. You read it and click Accept or Reject. There's no second confirmation waiting back in the terminal.&lt;/p&gt;

&lt;p&gt;The reject is the part I like. You don't just say no, you say why, in a sentence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdxhgff2rbulkairvmss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdxhgff2rbulkairvmss.png" alt="Rejecting an edit with a written reason" width="799" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That note goes straight back to the CLI, and Claude picks it up and tries again with your correction instead of taking another blind swing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp24s4juxterij2lm4kqt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp24s4juxterij2lm4kqt.png" alt="The same note, back in the CLI, getting acted on" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It reads your build too. Errors and warnings from the Error List, C# and C++, reach the model directly, so it can see what's broken without you pasting anything in. And there's a docked panel that keeps the session honest: what you've accepted and rejected, tokens and cost, and a line for how much the agent has been inspecting the debugger versus driving it. The switch that lets it drive lives here as well, off until you turn it on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2fpyv6mr35owwq2fisx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2fpyv6mr35owwq2fisx.png" alt="The panel: session stats, and the switch that gates anything that runs your code" width="688" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this took real work
&lt;/h2&gt;

&lt;p&gt;The obvious approach doesn't work, and that's most of the story.&lt;/p&gt;

&lt;p&gt;Claude Code talks to an IDE over a small local protocol: a lockfile plus a localhost WebSocket speaking MCP. The catch is that the CLI is picky about which of the IDE's tools it actually shows the model. It hands over diagnostics and runs the rest itself. So I could register a new "read the debugger" tool and the model would never be allowed to touch it.&lt;/p&gt;

&lt;p&gt;The debugger gets to Claude two other ways.&lt;/p&gt;

&lt;p&gt;The first is a hook. Send a prompt while you're paused at a breakpoint and a hook fires, reads the break state out of Visual Studio, and drops it into the context before the model sees your message. No tool call. Claude starts the turn already knowing where you're stopped and what every local holds.&lt;/p&gt;

&lt;p&gt;The second is a separate MCP server, the kind you register yourself for a project. Those aren't filtered, so any tool they expose, the model can call. The extension runs a small one for the debugger and registers it on launch. The work happens inside Visual Studio. A shim just shuttles messages to it.&lt;/p&gt;

&lt;p&gt;Reading state was pretty easy. Driving the debugger was the part that kept getting errors. "Step over" isn't a question with an instant answer. You issue it, then you have to wait for the program to stop again before the result means anything. Block the UI thread to wait and you freeze the IDE. The solution is to implement the step such that it goes out without blocking the main UI thread, the extension listens for the debugger's mode-change event, holds the request, and finishes it when the next break lands. From the model's side it's one clean call that returns the new state. Underneath it's an event handshake that keeps the editor responsive.&lt;/p&gt;

&lt;p&gt;Driving is also off by default. Reading runtime state is always on, since it can't break anything. Continue, step, breakpoints, starting and stopping a session: all of that sits behind the checkbox in the panel, and it resets every time you restart Visual Studio. Letting an agent run your code is a real decision, so its off by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A few things on the list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test driven debugging. Run the suite, and when a test fails, set a breakpoint at the failure, start debugging that test, and drive to the fault on its own.&lt;/li&gt;
&lt;li&gt;(Work in progress) Break on thrown. Stop the instant a chosen exception is thrown, at the throw site, not wherever it gets swallowed. &lt;/li&gt;
&lt;li&gt;CPU and memory. Point the .NET diagnostics tools at the running process and surface the hot methods and the heaviest allocations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's on the Visual Studio Marketplace, and the source is on GitHub. You need Visual Studio 2026 and the Claude Code CLI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Marketplace: &lt;a href="https://marketplace.visualstudio.com/items?itemName=firish.bridgev1" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=firish.bridgev1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source and docs: &lt;a href="https://github.com/firish/claude_code_vs" rel="noopener noreferrer"&gt;https://github.com/firish/claude_code_vs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work in Visual Studio and you've wanted Claude Code there, give it a try and tell me what breaks.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>ai</category>
      <category>visualstudio</category>
    </item>
  </channel>
</rss>
